Заменить несколько строк, идентифицирующих конечный символ

У меня есть код ниже

CREATE TABLE Table1(
        column1 double NOT NULL,
        column2 varchar(60) NULL,
        column3 varchar(60) NULL,
        column4 double NOT NULL,
 CONSTRAINT Index1 PRIMARY KEY CLUSTERED
(
        column2 ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON PRIMARY
) ON PRIMARY

GO
GO

и я хочу заменить

 CONSTRAINT Index1 PRIMARY KEY CLUSTERED
(
        column2 ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON PRIMARY
) ON PRIMARY

GO

с

)

Вы не можете предположить, что GO является последним символом файла. После Go может быть другая таблица script. Как я могу сделать это с помощью одного sed или awk.

Ответ 1

Update:

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

sed -r '/,/{N;/CONSTRAINT/{:a;N;/GO/!ba;s/([^,]+).*/\1\n)/};/CONSTRAINT/!n}' input.sql

Позвольте мне объяснить это как многострочный script:

# Search for a comma
/,/ {
  # If a command was found slurp in the next line
  # and append it to the current line in pattern buffer
  N
  # If the pattern buffer does not contain the word CONSTRAINT
  # print the pattern buffer and go on with the next line of input
  # meaning start searching for a comma
  /CONSTRAINT/! n

  # If the pattern CONSTRAINT was found we loop until we find the 
  # word GO
  /CONSTRAINT/ {
    # Define a start label for the loop 
    :a
    # Append the next line of input to the pattern buffer
    N
    # If GO is still not found in the pattern buffern
    # step to the start label of the loop
    /GO/! ba

    # The loop was exited meaning the pattern GO was found.
    # We keep the first line of the pattern buffer - without
    # the comma at the end and replace everything else by a )
    s/([^,]+).*/\1\n)/
  }
}

Вы можете сохранить указанный выше многострочный файл script в файле и выполнить его с помощью

sed -rf script.sed input.sql

Вы можете использовать следующую команду sed:

sed '/CONSTRAINT/{:a;N;/GO/!ba;s/.*/)/}' input.sql

Образец ищет строку, содержащую /CONSTRAINT/. Если шаблон найден, блок команд запускается между { }. В блоке мы сначала определяем метку a через :a. Мы получаем следующую строку ввода через N и добавляем ее в буфер шаблонов. Если мы не найдем шаблон /GO/!, мы продолжим работу с меткой a с помощью команды branch b. Если найден шаблон /GO/, мы просто заменим буфер на ).


Альтернатива может использоваться с использованием диапазона, например, предложенного FredPhil:

sed '/CONSTRAINT/,/GO/{s/GO/)/;te;d;:e}'

Ответ 2

С GNU awk для multi- char RS и предполагая, что вы хотите избавиться от запятой перед "CONSTRAINT":

$ cat tst.awk
BEGIN{ RS="^$"; ORS="" }
{
    gsub(/\<GO\>/,"\034")
    gsub(/,\s*CONSTRAINT[^\034]+\034/,")")
    gsub(/\034/,"GO")
    print
}
$ gawk -f tst.awk file
CREATE TABLE Table1(
        column1 double NOT NULL,
        column2 varchar(60) NULL,
        column3 varchar(60) NULL,
        column4 double NOT NULL)
GO

Вышеупомянутое работает путем замены каждого автономного "GO" с помощью элемента управления char, который вряд ли появится на вашем входе (в этом случае я использовал то же значение, что и SUBSEP по умолчанию), поэтому мы можем использовать этот char в списке отрицательных символов в середине gsub() для создания регулярного выражения, которое заканчивается первым "GO" после "CONSTRAINT". Это один из способов сделать "неживое" соответствие в awk.

Если не существует char, который вы не знаете на своем входе, вы можете создать его следующим образом:

$ cat tst.awk
BEGIN{ RS="^$"; ORS="" }
{
    gsub(/a/,"aA"); gsub(/b/,"aB"); gsub(/\<GO\>/,"b")
    gsub(/,\s*CONSTRAINT[^b]+b/,")")
    gsub(/b/,"GO"); gsub(/aB/,"b"); gsub(/aA/,"a")
    print
}
$ 
$ gawk -f tst.awk file
CREATE TABLE Table1(
        column1 double NOT NULL,
        column2 varchar(60) NULL,
        column3 varchar(60) NULL,
        column4 double NOT NULL)
GO

Вышеизложенное изначально преобразует все "a" в "aA" и "b" в "aB", так что

  • в записи больше нет "b" , а
  • так как все оригинальные "а" теперь имеют после них "А", единственные вхождения "aB" представляют, где "bs" изначально были расположены

а это значит, что теперь мы можем преобразовать все "GO" в "b" так же, как мы перевели их в "\ 034" в первом script выше. Затем мы делаем главный gsub(), а затем разворачиваем наш начальный gsub() s.

Эта идея gsub() ing создавать символы, которые ранее не существовали, используя эти символы, а затем развертывание начального gsub() - чрезвычайно полезная идиома, чтобы учиться и запоминать, например. см. fooobar.com/questions/8472/... для другого приложения.

Чтобы увидеть, как он работает шаг за шагом:

$ cat file                                                                                                   
foo bar Hello World World able bodies

$ awk '{gsub(/a/,"aA")}1' file                                                                               
foo baAr Hello World World aAble bodies

$ awk '{gsub(/a/,"aA"); gsub(/b/,"aB")}1' file                                                               
foo aBaAr Hello World World aAaBle aBodies

$ awk '{gsub(/a/,"aA"); gsub(/b/,"aB"); gsub(/World/,"b")}1' file                                            
foo aBaAr Hello b b aAaBle aBodies

$ awk '{gsub(/a/,"aA"); gsub(/b/,"aB"); gsub(/World/,"b"); gsub(/Hello[^b]+b/,"We Are The")}1' file                         
foo aBaAr We Are The b aAaBle aBodies

$ awk '{gsub(/a/,"aA"); gsub(/b/,"aB"); gsub(/World/,"b"); gsub(/Hello[^b]+b/,"We Are The"); gsub(/b/,"World")}1' file 
foo aBaAr We Are The World aAaBle aBodies

$ awk '{gsub(/a/,"aA"); gsub(/b/,"aB"); gsub(/World/,"b"); gsub(/Hello[^b]+b/,"We Are The"); gsub(/b/,"World"); gsub(/aB/,"b")}1' file
foo baAr We Are The World aAble bodies

$ awk '{gsub(/a/,"aA"); gsub(/b/,"aB"); gsub(/World/,"b"); gsub(/Hello[^b]+b/,"We Are The"); gsub(/b/,"World"); gsub(/aB/,"b"); ; gsub(/aA/,"a")}1' file
foo bar We Are The World able bodies

Ответ 3

Это может выглядеть страшно, но нетрудно понять немного:

SED_DELIM=$(echo -en "\001")
START=' CONSTRAINT Index1 PRIMARY KEY CLUSTERED'
END='GO' 
sed -n $'\x5c'"${SED_DELIM}${START}${SED_DELIM},"$'\x5c'"${SED_DELIM}${END}${SED_DELIM}{s${SED_DELIM}GO${SED_DELIM})${SED_DELIM};t a;d;:a;};p" test2.txt

У sed есть следующая форма, с которой вы можете быть знакомы:
 sed /regex1/,/regex2/{commands}

Сначала он использует SOH без печати в качестве разделителя \001
Устанавливает теги START и END для sed multiline match
Затем выполняет команду sed:
-n не печатать по умолчанию
$'\x5c' - это строковый литерал Bash, который соответствует обратному косую черту \
Обратные косые черты необходимы, чтобы избежать непечатаемого разделителя в многострочном диапазоне соответствия.
{s${SED_DELIM}GO${SED_DELIM})${SED_DELIM};t a;d;:a;};p:
 s${SED_DELIM}GO${SED_DELIM})${SED_DELIM} замените строку, соответствующую GO, с помощью )
 t a;, если в предыдущем утверждении есть успешная замена, а затем перейдите к метке :a    d, если нет подстановки, тогда удалите строку     p распечатать любой результат после команд перейдите к

Я не видел их ответов до публикации этого вопроса - этот ответ такой же, как у FredPhil/hek2mgl - за исключением того, что у вас есть механизм, который будет более динамичным на LHS, поскольку вы можете изменить разделитель на символ, который гораздо меньше шансов появиться в наборе данных.