bash / sed, заменить строку в файле с тем же уровнем отступа

#bash #shell #awk #sed

#bash #оболочка #awk #sed

Вопрос:

Я ищу замену строк в файле. т.е., Если «oldtext» находится в файле в начале строки, замените всю строку на «newtext».

Начало строки может иметь ^oldtext или oldtext будет иметь отступ с некоторыми пробелами / табуляциями

 grep "^[S]oldtext" myfile.txt   # doesn't work
  

Мне нужно вычислить тот же уровень отступа, что и у «oldtext»

 prefixspaces = <number of spaces before the "mytext", converting tabs to 4 spaces>
  

Я понимаю, что sed, возможно, лучший способ добиться этого, но мне как-то нужно сначала вычислить пространство префиксов, затем мой вывод выглядит примерно так:

 sed -i -e 's/^$prefixspaces$newtext*$/$prefixspaces$newtext/g' myfile.txt
  

Не могу понять, как это вычислить. $prefixspace Может быть, его можно хранить в группе () или что-то в этом роде, чтобы sed мог легко вычислять и работать с ним? Возможно, это однострочный sed или awk, но я не могу разобраться с этим после некоторого поиска.

Комментарии:

1. Не могли бы вы также опубликовать образец ввода и ожидаемого результата в своем вопросе, чтобы сделать его более понятным.

2. s/^([[:space:]]*)oldtext/1$newtext/ ?

3. Это здорово, @Shawn, я добавил .* после oldtext, чтобы он заменял всю строку, и хотел, чтобы все пробелы были, поэтому изменил его на s* в начале строки. Работает хорошо.

4. Вы должны проверить ответы, которые вы получили, когда oldtext содержат метасимволы регулярных выражений и newtext содержат обратные ссылки. Например, попробуйте их, когда oldtext есть a.c , и входной файл содержит оба a.c и abc , и когда newtext есть damp;e . Вы должны выполнять буквальное сопоставление строк и замены буквенных строк, а не сопоставление регулярных выражений и замены строк с поддержкой обратной ссылки, которые поддерживают все, что поддерживает sed, и пока все ответы.

5. хорошо, у вас есть способ исправить это изменение?

Ответ №1:

Вот так:

 sed 's/^( *)oldtext.*/1newtext/' file
  

Объяснение:

Поиск

 ^             beginning of the line
( *)        capture 0 or more spaces in group 1
oldtext       'oldtext'
.*            rest of the line
  

заменить

 1            the 0 or more spaces from group1
newtext       'newtext'
  

Ответ №2:

С sed

Другой способ сделать, дополнительно к принятому ответу, — использовать символы класса ([[:пробел:]]) и расширяющие регулярные выражения с -r параметром:

 $ sed -r 's/^([[:space:]]*)oldtext.*/1newtext/' << _eof
  oldtext this
 a
    oldtext also this
_eof
  

И результат будет:

   newtext
 a
    newtext
  

В ([[:space:]]*) , мы захватили группу с нулем или более пробелов, но мы можем указать определенное число.

С awk

Мы можем использовать gsub для замены пробелов и подсчета времени:

 $ awk 
    -v old="oldtext" 
    -v new="newtext" 
    'BEGIN{re="^[[:space:]]*"old}
{
  if (match($0,re)) {
    i=gsub(/ /,"")
    while (--i) printf " "
    print new
  }
  else 
    print
}' << _eof
  oldtext this
oldtext also this
    oldtext and this
  oldtext also this
_eof
  

И результат будет:

   newtext
 newtext
     newtext
   newtext
  

С perl

Мы можем достичь этого с помощью perl в стиле, подобном sed:

 $ perl -lpe 's/^(s*)oldtext.*/1newtext/' << _eof
  oldtext this
oldtext this
    oldtext this
  oldtext also this
_eof
  

И результат будет:

   newtext
newtext
    newtext
  newtext
  

Ответ №3:

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

 sed '/^s*oldtext/s/S.*/newtext/' file
  

Сопоставление oldtext в начале строки или с отступом и замена с первого символа, не являющегося пробелом.