Предположим, что данные представляют собой правильно сформированный 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>
Я решил прочитать вывод моего оператора grep
в массив и получить доступ к этим элементам по отдельности, а не создавать множество переменных.
acl_stats_trusted=( $(grep "Trusted" "$expect_results") )
echo "${acl_stats_trusted[1]}" # Outputs 1
echo "${acl_stats_trusted[3]}" # Outputs 1311578
Это намного короче и понятнее для любого пользователя.
Вы можете использовать встроенную функцию 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 произойдет, если во входных данных не будет найдена совпадающая строка.
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-скрипт должен работать и для них, так как -нет необходимости создавать для них специализированные сценарии оболочки. Для входных файлов в других форматах вы можете настроить скрипт, чтобы использовать тот же подход.