'sed' заменить последний шаблон и удалить другой шаблон

Я хочу заменить в моем файле только последнюю строку "delay" на "ens_delay" и удалить остальные строки перед последней:

Входной файл:

alpha_notify_teta=''
alpha_notify_check='YES'
text='CRDS'
delay=''
delay=''
delay=''
textfileooooop=''
alpha_enable='YES'
alpha_hostnames=''
alpha_orange='YES'
alpha_orange_interval='300'
alpha_notification_level='ALL'
expression='YES'
delay='9'
textfileooooop=''
alpha_enable='YES'
alpha_hostnames=''

Выходной файл: (ожидаемое значение)

alpha_notify_teta=''
alpha_notify_check='YES'
text='CRDS'

textfileooooop=''
alpha_enable='YES'
alpha_hostnames=''
alpha_orange='YES'
alpha_orange_interval='300'
alpha_notification_level='ALL'
expression='YES'
ens_delay='9'
textfileooooop=''
alpha_enable='YES'
alpha_hostnames=''

Вот моя первая команда, но она не работает, потому что она будет работать только в том случае, если у меня есть задержка в последней строке.

sed -e '$,/delay/ s/delay/ens_delay/'

Моя вторая команда удалит все строки, содержащие "delay", даже "ens_delay" будет удалено.

sed -i '/delay/d'

Спасибо

В sed нет хорошего способа узнать, достигли ли вы последнего появления некоторого шаблона в нескольких строках.

sorpigal 13.11.2018 21:50

Он изменяет только последнюю строку, потому что вы сообщаете об этом: $ - это адрес последней строки, и вы используете его как первую строку диапазона адресов.

glenn jackman 13.11.2018 22:09

Вы хотите, чтобы не последние строки delay = превратились в одну пустую строку? Достаточно ли будет полностью исчезнуть? Достаточно ли просто быть пустым?

sorpigal 13.11.2018 22:17

Вы можете отсортировать файл? Что, если один и тот же ключ встречается с разными значениями? Достаточно ли мал файл, чтобы можно было использовать медленную логику? Нужно ли это делать?

Paul Hodges 13.11.2018 23:19

Кроме того, здесь отсутствуют детали. vtsisc_delay не встречается ни в одном из них. ens_delay встречается только в ожидаемом вами выходе. Где код в контексте?

Paul Hodges 13.11.2018 23:25

@PaulHodges Я уже внес некоторые исправления

Rata 14.11.2018 13:34

Покажите нам простейший ~ полный пример кода и его выходных данных и поясните свои требования. Вы хотите, чтобы 1) все, кроме ПОСЛЕДНЕЙ строки в файле, содержащие строку delay, были удалены, без каких-либо дополнительных особых условий? и 2) вы хотите, чтобы эта строка добавляла ens_ к delay, да? И порядок строк специфичен, или вы могли бы отсортировать файл? Что еще более важно, насколько велик файл? Это единственная строка, для которой вы хотите удалить дубликаты?

Paul Hodges 14.11.2018 15:35
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
7
139
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

Вы не можете делать такие вещи с помощью sed. В sed нет способа «заглянуть вперед» и сказать, есть ли еще совпадения с шаблоном. Можно как бы оглянуться назад, но этого недостаточно для решения проблемы.

Этот сценарий Perl решит эту проблему:

#!/usr/bin/perl
use strict;
use warnings;
my ($seek, $replacement, $last, @new) = (shift, shift, 0);
open(my $fh, shift) or die $!;
my @l = <$fh>;
close($fh) or die $!;
foreach (reverse @l){
    if (/$seek/){
        if ($last++ == 0){
            s/$seek/$replacement/;
        } else {
            next;
        }
    }
    unshift(@new, $_);
}
print join "", @new;

Звоните как:

./script delay= ens_delay= inputfile

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

Вы также можете решить эту проблему с помощью awk, python или любого количества других языков. Только не sed.

У этого монстра:

sed -e "1,$(expr $(sed -n '/^delay=/=' your_file.txt | tail -1) - 1)"'s/^delay=.*$//' \
    -e 's/^delay=/ens_delay=/' your_file.txt

Здесь:

  • sed -n '/^delay=/=' your_file.txt | tail -1 возвращает последнюю строку номер обнаруженного шаблона (назовем его X)
  • expr используется для получения линии X-1
  • "1,X-1"'[command]' означает «выполнить эту команду между первой строкой и включенной строкой X-1 (я использовал двойные кавычки, чтобы расширение было выполнено).
  • 's/^delay=.*$//' упомянутый [command]
  • -e 's/^delay=/ens_delay=/' следующее выражение для выполнения (встречается только в последней строке)

Вывод:

alpha_notify_teta=''
alpha_notify_check='YES'
text='CRDS'



textfileooooop=''
alpha_enable='YES'
alpha_hostnames=''
alpha_hsm_backup_notification='YES'
alpha_orange='YES'
alpha_orange_interval='300'
alpha_notification_level='ALL'
expression='YES'
ens_delay='9'
textfileooooop=''
alpha_enable='YES'
alpha_hostnames=''
alpha_hsm_backup_notification='YES'

Если вы хотите удалить строки вместо того, чтобы оставить их пустыми:

sed -e "1,$(expr $(sed -n '/^delay=/=' your_file.txt | tail -1) - 1)"'{/^delay=.*$/d}' \
    -e 's/^delay=/ens_delay=/' your_file.txt

можешь сказать мне, что здесь делает test.txt раньше | хвостик ? потому что у меня ошибка sed: can't read test.txt: No such file or directory expr: syntax error sed: -e expression #1, char 3: unexpected , спасибо

Rata 14.11.2018 01:21

Я заменил только your_file.txt своим файлом iinput, так что я должен делать с test.txt

Rata 14.11.2018 01:25

он удаляет только дубликат и сохраняет последнее слово, не заменяя его. Спасибо

Rata 14.11.2018 14:37

@Hamadagm, он заменяет последнее слово, у меня на выходе есть ens_delay='9'.

Amessihel 14.11.2018 21:04

Как уже упоминалось в другом месте, sed не может знать, какое вхождение подстроки является последним. Но awk может отслеживать вещи в массивах. Например, следующее удалит все повторяющиеся назначения, а также попросит сделать вашу замену:

awk 'BEGIN{FS=OFS = " = "} $1= = "delay"{$1 = "ens_delay"} !($1 in a){o[++i]=$1} {a[$1]=$0} END{for(x=0;x<i;x++) printf "%s\n",a[o[x]]}' inputfile

Или, разбитый для облегчения чтения / комментариев:

BEGIN {
  FS=OFS = " = "       # set the field separator, to help isolate the left hand side
}

$1= = "delay" {
  $1 = "ens_delay"   # your field substitution
}

!($1 in a) {
  o[++i]=$1        # if we haven't seen this variable, record its position
}

{
  a[$1]=$0         # record the value of the last-seen occurrence of this variable
}

END {
  for (x=0;x<i;x++)          # step through the array,
    printf "%s\n",a[o[x]]    # printing the last-seen values, in the order
}                            # their variable was first seen in the input file.

Возможно, вас не волнует порядок переменных. Если да, то может быть проще следующее:

awk 'BEGIN{FS=OFS = " = "} $1= = "delay"{$1 = "ens_delay"} {o[$1]=$0} END{for(i in o) printf "%s\n", o[i]}' inputfile

Это просто сохраняет последнюю увиденную строку в массиве, ключ которого является именем переменной, а затем распечатывает содержимое массива в неизвестном порядке.

он работает, но порядок строк в моем файле изменился, и как я могу сохранить изменения после этой командной строки. Спасибо

Rata 14.11.2018 14:30

Я устал от вашей последней команды соблюдать порядок моих строк, но я потерял порядок. Спасибо

Rata 14.11.2018 15:24

@Hamadagm, да, это то, что я сказал в ответе. Если вас не волнует порядок, используйте последнюю версию, которая не сохраняет порядок вывода. Если вы ДЕЙСТВИТЕЛЬНО заботитесь о порядке вывода, используйте первую версию, которая поддерживает отдельный массив (o[]) для сохранения порядка.

ghoti 14.11.2018 15:44

Если я правильно понимаю ваши спецификации, это должно сделать то, что вам нужно. Учитывая infile x,

$: last=$( grep -n delay x|tail -1|sed 's/:.*//' )

Этот grep является файлом для всех строк с delay и возвращает их с номером строки с двоеточием в начале. tail -1 захватывает последнюю из этих строк, игнорируя все остальные. sed 's/:.*//' удаляет двоеточие и фактическое содержимое строки, оставляя только номер (здесь это был 14).

Все это дает оценку 14 как $last.

$: sed '/delay/ { '$last'!d; '$last' s/delay/ens_delay/; }' x
alpha_notify_teta=''
alpha_notify_check='YES'
text='CRDS'
textfileooooop=''
alpha_enable='YES'
alpha_hostnames=''
alpha_orange='YES'
alpha_orange_interval='300'
alpha_notification_level='ALL'
expression='YES'
ens_delay='9'
textfileooooop=''
alpha_enable='YES'
alpha_hostnames=''

Прошу прощения за уродливую связь. Он записывает сценарий, используя значение $last, чтобы результат для sed выглядел так:

$: sed '/delay/ { 14!d; 14 s/delay/ens_delay/; }' x

sed считывает ведущие числа как селекторы строк, поэтому что делает этот сценарий команд -

Во-первых, sed автоматически печатает строки, если не указано иное, поэтому по умолчанию он просто печатает каждую строку. Сценарий изменяет это.

/delay/ { ... } - это селектор записи на основе шаблонов. Он будет применять команды между {} ко всем строкам, которые соответствуют /delay/, поэтому ему не нужен другой grep - он обрабатывает это сам. Внутри фигурных скобок сценарий выполняет две функции.

Во-первых, 14!d говорит (только если в этой строке есть delay, а он будет), что если номер строки 14, выполните нет (!) delete запись. Поскольку все остальные строки с delay не будут строкой 14 (или любым другим значением последней, созданной предыдущей командой), они получат deleted, который автоматически перезапустит цикл и прочитает следующую запись.

Во-вторых, если номер строки является 14, то delete не удалится, и поэтому перейдет к s/delay/ens_delay/, который обновит ваше значение.

Для всех строк, которые не соответствуют /delay/, sed просто печатает их как есть.

ваша команда работает правильно, но я хочу сохранить свои изменения в файле после выполнения этой команды: last=$( grep -n delay x|tail -1|sed 's/:.*//' ) | sed '/delay/ { '$last'!d; '$last' s/delay/ens_delay/; }' x Чем вы

Rata 14.11.2018 17:03

и я вижу, что только sed работает sed '/delay/ { '$last'!d; '$last' s/delay/ens_delay/; }' x, можете ли вы объяснить, нужно ли мне тоже использовать grep?

Rata 14.11.2018 17:10

Теперь он работает, но вот так и не требует grep sed -i '/delay/ { '$last'!d; '$last' s/delay/ens_delay/; }' x. Вы можете объяснить, почему не нужен grep, спасибо

Rata 14.11.2018 17:17

если вы один раз запустили строку last=$( grep -n delay x|tail -1|sed 's/:.*//' ), то last все равно должен быть установлен в вашей среде. Попробуйте так: echo $last - должен быть номер строки. затем попробуйте это - unset last - и запустите снова. Скорее всего, не получится. Добавлю некоторые пояснения к решению. Когда вы будете готовы сделать изменения постоянными, либо перенаправьте вывод в новый файл, либо добавьте аргумент -i в sed.

Paul Hodges 14.11.2018 20:38
Ответ принят как подходящий

Это может сработать для вас (GNU sed):

sed '/^delay=/,$!b;/^delay=/!H;//{x;s/^[^\n]*\n\?//;/./p;x;h};$!d;x;s/^/ens_/' file

Строки перед началом первой строки delay= следует печатать как обычно. В противном случае строка, начинающаяся с delay=, сохраняется в удерживаемом пространстве, а последующие строки, которые не начинаются с delay=, добавляются к ней. Если пространство удержания уже содержит такие строки, первая строка удаляется, а оставшиеся строки печатаются до того, как пространство удержания заменяется текущей строкой. В конце файла первая строка удерживаемого пространства изменяется, чтобы добавить строку ens_, а затем печатается все удерживаемое пространство.

Другие вопросы по теме