Установите несколько переменных в разные поля вывода awk

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

<?xml version="1.0"?>
<root>
  <ipaddr>192.168.1.2</ipaddr>
  <subnet>24</subnet>
  <ipaddr>192.168.1.1</ipaddr>
  <subnet>24</subnet>
</root>

Следующая команда XMLStarlet найдет узел ipaddrс текущим значением 192.168.1.1и тот, чей первый родственный узел subnetравен 24, и изменит значение этого узла ipaddrна 192.168.1.125. Затем он сделает то же самое, чтобы изменить значение узла subnetна 25, используя новое значение узла ipaddr, чтобы найти его.

xmlstarlet ed \
    -u '//ipaddr[. = "192.168.1.1" and following-sibling::subnet[1] = "24"]' -v '192.168.1.125' \
    -u '//subnet[. = "24" and preceding-sibling::ipaddr[1] = "192.168.1.125"]' -v 25 file.xml

Результатом будет

<?xml version="1.0"?>
<root>
  <ipaddr>192.168.1.2</ipaddr>
  <subnet>24</subnet>
  <ipaddr>192.168.1.125</ipaddr>
  <subnet>25</subnet>
</root>

Команда была бы намного проще и менее подвержена ошибкам, если бы узлы ipaddrи subnetбыли дочерними узлами одного и того же родительского узла, как в

<?xml version="1.0"?>
<root>
  <host name="hostA">
    <ipaddr>192.168.1.2</ipaddr>
    <subnet>24</subnet>
  </host>
  <host name="hostB">
    <ipaddr>192.168.1.1</ipaddr>
    <subnet>24</subnet>
  </host>
</root>

Затем мы могли бы просто выбрать имя узла hostвот так:

xmlstarlet ed \
    -u '//host[@name="hostB"]/ipaddr' -v '192.168.1.125' \
    -u '//host[@name="hostB"]/subnet' -v '25' file.xml

производить

<?xml version="1.0"?>
<root>
  <host name="hostA">
    <ipaddr>192.168.1.2</ipaddr>
    <subnet>24</subnet>
  </host>
  <host name="hostB">
    <ipaddr>192.168.1.125</ipaddr>
    <subnet>25</subnet>
  </host>
</root>
0
09.12.2020, 17:37
3 ответа

Я решил прочитать вывод моего оператора grepв массив и получить доступ к этим элементам по отдельности, а не создавать множество переменных.

acl_stats_trusted=( $(grep "Trusted" "$expect_results") )
echo "${acl_stats_trusted[1]}" # Outputs 1
echo "${acl_stats_trusted[3]}" # Outputs 1311578

Это намного короче и понятнее для любого пользователя.

1
18.03.2021, 22:44

Вы можете использовать встроенную функцию readдля чтения строки и присвоения нескольких значений переменным.

Пример с обработкой ошибок и более короткими именами переменных:

if read dummy var1 var2 var3 var4 var5 var6 var7 extra
then
  echo OK
else
  echo EOF
fi < <(grep -A 2 "Slot 0 /Port 0" $expect_results | grep "Trusted")
if [ -n "$extra" ]
then
  echo too many values
fi
if [ -z "$var7" ]
then
  echo not enough values
fi
echo vars: $var1 $var2 $var3 $var4 $var5 $var6 $var7

или короче без обработки ошибок

read dummy var1 var2 var3 var4 var5 var6 var7 extra < <(grep -A 2 "Slot 0 /Port 0" $expect_results | grep "Trusted")
echo vars: $var1 $var2 $var3 $var4 $var5 $var6 $var7             

Код предполагает, что введенные вами данные состоят из одной строки, содержащей простые значения, разделенные пробелами, без встроенных пробелов, специальных символов, кавычек...

Чтобы понять, как обрабатывать ошибки для EOF или неправильного количества значений, прочтите документация встроенного read, например. здесьhttps://www.gnu.org/software/bash/manual/html_node/Bash-Builtins.html

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

1
18.03.2021, 22:44

FWIW Я бы просто использовал awk для извлечения текста, а также значений из входного файла и вывода его в файл.prom без необходимости вручную создавать кучу переменных оболочки и т. д.:

$ cat tst.awk
$1 ~ /^-+$/ {
    #    -------------------- ACL Stats Per Interface ----------------------
    gsub(/^[[:space:]]*-+[[:space:]]+|[[:space:]]+-+[[:space:]]*$/,"")
    fileHdr = $0
    next
}

/^[[:space:]]/ {
    if (NF == 3) {
        #    Entries         Packets                         Dropped
        colName[1] = $1
        for (i=2; i<=NF; i++) {
            colHdr[i] = $i
        }
    }
    else {
        #        Recent      Total  PerMax      Recent    Total    PerMax
        for (i=1; i<=3; i++) {
            colName[i+1] = $i "_" colHdr[2]
        }
        for (; i<=NF; i++) {
            colName[i+1] = $i "_" colHdr[3]
        }
    }
    next
}

/^Slot/ {
    # Slot 0 /Port 0
    slot = $2
    port = $NF
    next
}

/^[[:alpha:]]/ {
    # Trusted              1         196    1311578     386           0          0       0
    # Untrusted            3          20  217217953  852794           0          0       0
    rowName = $1
    for (i=2; i<=NF; i++) {
        out = tolower(fileHdr "_" rowName "_" colName[i-1] "_s" slot "_p" port) "=" $i
        gsub(/[[:space:]]+/,"_",out)
        print out
    }
}

$ awk -f tst.awk file
acl_stats_per_interface_trusted_entries_s0_p0=1
acl_stats_per_interface_trusted_recent_packets_s0_p0=196
acl_stats_per_interface_trusted_total_packets_s0_p0=1311578
acl_stats_per_interface_trusted_permax_packets_s0_p0=386
acl_stats_per_interface_trusted_recent_dropped_s0_p0=0
acl_stats_per_interface_trusted_total_dropped_s0_p0=0
acl_stats_per_interface_trusted_permax_dropped_s0_p0=0
acl_stats_per_interface_untrusted_entries_s0_p0=3
acl_stats_per_interface_untrusted_recent_packets_s0_p0=20
acl_stats_per_interface_untrusted_total_packets_s0_p0=217217953
acl_stats_per_interface_untrusted_permax_packets_s0_p0=852794
acl_stats_per_interface_untrusted_recent_dropped_s0_p0=0
acl_stats_per_interface_untrusted_total_dropped_s0_p0=0
acl_stats_per_interface_untrusted_permax_dropped_s0_p0=0

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

2
18.03.2021, 22:44

Теги

Похожие вопросы