Определите строку в скрипте bash иерархического файла

#awk #sed #grep

Вопрос:

Мне нужно определить заголовок списка записей конфигурации в файле. Там не предсказуемо, это может быть любая строка, но это всегда будет строка, которая начинается ближе слева, чем другие (исключая «выход»).:

Вот пример:

     vpls 2662 customer 1 v-vpls vlan 2662 create
        description "RES_2662"
        mac-move
            allow-res-res
            allow-reg-res
        exit
        stp
            shutdown
        exit
        ingress
            qos 2
        exit
        sap lt:1/1/1:2662 create
            description "RES_2662"
            enable-stats
            no shutdown
        exit
        sap lag-1:2662 create
            no shutdown
        exit
        no shutdown
    exit
    vpls 2663 customer 1 v-vpls vlan 2663 create
        description "RES_2663"
        mac-move
            allow-res-res
            allow-reg-res
        exit
        stp
            shutdown
        exit
        ingress
            qos 2
        exit
        sap lt:1/1/1:2663 create
            description "RES_2663"
            enable-stats
            no shutdown
        exit
        sap lag-1:2663 create
            no shutdown
 

В этом случае мне нужно уметь идентифицировать две строки, которые начинаются с:
vpls 266X customer 1 v-vpls vlan 266X create
Сценарий должен знать, что это те строки, которые я ищу.

На выходе не всегда будут отображаться пробелы слева, как в этом примере:

 port vlan-port:1/1/1/3/7/4/4:824
  admin-up
  severity no-value
exit
port vlan-port:1/1/1/3/7/4/4:1224
  admin-up
  severity no-value
exit
 

В этом случае желаемыми строками являются:
port vlan-port:x/x/x/x/x/x/x/x

Я не знаю, можно ли это сделать с помощью grep/sed/awk.

Спасибо за вашу помощь.

Ответ №1:

Следующее будет работать с использованием любого awk в любой оболочке на каждом блоке Unix и сохранит порядок строк ввода для вывода, если это имеет значение:

 $ cat tst.awk
$1 != "exit" {
    match($0,/^ */)
    if ( (min == "") || (RLENGTH <= min) ) {
        min = RLENGTH
        lines[min,  cnt[min]] = $0
    }
}
END {
    for (i=1; i<=cnt[min]; i  ) {
        print lines[min,i]
    }
}
 
 $ awk -f tst.awk file
    vpls 2662 customer 1 v-vpls vlan 2662 create
    vpls 2663 customer 1 v-vpls vlan 2663 create
 

Ответ №2:

Я подозреваю, что есть лучшие способы сделать это, но моей первой мыслью было следующее. Возможно, вы могли бы начать с чего-то подобного и улучшить его.

 minl=$(awk '{match($0, /^ */);if (NR==1 || RLENGTH<minl) {minl=RLENGTH}} END{print minl}' test.txt)
sed -n "/^[ ]{${minl}}[^ ]/p" test.txt | grep -v "exit"
 

Первая строка используется awk для получения минимального количества пробелов в начале строки для файла.

Вторая строка используется sed для сопоставления строк, начинающихся с количества пробелов, вычисленных в первой строке. Я передал результат этого по grep -v "exit" каналу, чтобы избавиться от выходных линий… возможно, вам потребуется более строгая проверка, может ли допустимая строка вывода содержать текст «выход».

Ответ №3:

Еще одно возможное решение

 # gets the number of leading spaces   1
n=$(sort -r file.txt | sed -nE '1s/(^ *).*/1/p' | wc -c | tr -d ' ')
# filter the file
egrep -vE "^ {$n,}|^ *exit" file.txt
 

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

1. Это хорошо работает с первым примером, но не со вторым…

2. для меня это производит port vlan-port:1/1/1/3/7/4/4:824 port vlan-port:1/1/1/3/7/4/4:1224 то, что кажется ожидаемым, каков ваш результат?

Ответ №4:

Допущения:

  • начальный пробел состоит исключительно из пробелов (т. е. Без вкладок, без непечатаемых символов).

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

 awk '
BEGIN   { min = 9999999 }

/^$/    { exit }                  # skip blank lines

/exit/  { if {NF==1) next }       # skip lines with single field "exit"

        { n = match($0,/[^ ]/)    # find index of first non-space

          if ( n < min ) {        # if a new minimum is found then ...
             delete arr           # delete the array and ...
             i = 1                # reset the array index and ...
             min = n              # reset the min
          }

          if ( n == min )         # if current row matches with "min" then ...
             arr[i  ] = $0        # save the row in our array; increment the index
        }

END     { for (j=1;j<i;j  )       # loop through entries in array
             print arr[j]
        }
' file.dat
 

Для 1-го набора данных OP это генерирует:

     vpls 2662 customer 1 v-vpls vlan 2662 create
    vpls 2663 customer 1 v-vpls vlan 2663 create
 

Для 2-го набора данных OP это генерирует:

 port vlan-port:1/1/1/3/7/4/4:824
port vlan-port:1/1/1/3/7/4/4:1224
 

Ответ №5:

Попробуйте это perl и awk комбинацию:

 $ perl -ne ' /(^.s*)/ and !/^s*exit/ and print length($1), $_ ' fernando.txt | sort -n | awk ' { f=$1; p=NR==1?f:p; sub(/^[0-9] /,"",$0);if(f==p)  print } '
    vpls 2662 customer 1 v-vpls vlan 2662 create
    vpls 2663 customer 1 v-vpls vlan 2663 create