Как вставить новый элемент под другим с помощью xmlstarlet?
$ vim test.xml
<?xml version="1.0" encoding="UTF-8" ?>
<config>
</config>
$ xmlstarlet ed -i "/config" -t elem -n "sub" -v "" test.xml
<?xml version="1.0" encoding="UTF-8"?>
<sub></sub>
<config>
</config>
Но я хотел, чтобы sub был потомком конфига. Как мне изменить параметр xpath -i?
БОНУС: Возможно ли вставить дочерний элемент непосредственно с атрибутом и даже установить для него значение? Что-то вроде:
$ xmlstarlet ed -i "/config" -t elem -n "sub" -v "" -a attr -n "class" -v "com.foo" test.xml
6 ответов
Использование -s
(или же --subnode
) вместо -i
, Что касается бонуса, вы не можете вставить элемент с атрибутом напрямую, но, поскольку каждая операция редактирования выполняется последовательно, чтобы вставить элемент, а затем добавить атрибут:
> xml ed -s /config -t elem -n sub -v "" -i /config/sub -t attr -n class -v com.foo test.xml
<?xml version="1.0" encoding="UTF-8"?>
<config>
<sub class="com.foo"></sub></config>
У меня была похожая проблема: у меня был файл конфигурации Tomcat (server.xml), и мне пришлось вставить <Resource>
тег с предопределенными атрибутами в <GlobalNamingResources>
раздел.
Вот как это выглядело раньше:
<GlobalNamingResources>
<!-- Editable user database that can also be used
by UserDatabaseRealm to authenticate users
-->
<Resource name="UserDatabase"
auth="Container"
type="org.apache.catalina.UserDatabase"
description="User database that can be updated and saved"
factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
pathname="conf/tomcat-users.xml" />
</GlobalNamingResources>
Вот чего я хотел добиться:
<GlobalNamingResources>
<!-- Editable user database that can also be used
by UserDatabaseRealm to authenticate users
-->
<Resource name="UserDatabase"
auth="Container"
type="org.apache.catalina.UserDatabase"
description="User database that can be updated and saved"
factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
pathname="conf/tomcat-users.xml" />
<Resource name="jdbc/templateassets"
auth="Container"
type="javax.sql.DataSource"
driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://DBHOST:DBPORT/DBNAME?createDatabaseIfNotExist=false&useUnicode=true&characterEncoding=utf-8"
username="DBUSER"
password="DBPASS"
maxActive="150"
maxIdle="10"
initialSize="10"
validationQuery="SELECT 1"
testOnBorrow="true" />
</GlobalNamingResources>
Вот как я это сделал (фрагмент из скрипта оболочки):
if [ -n "$(xmlstarlet sel -T -t -v "/Server/GlobalNamingResources/Resource[@name='jdbc/templateassets']/@name" server.xml)" ]; then
echo "Resource jdbc/templateassets already defined in server.xml"
else
echo "Adding resource jdbc/templateassets to <GlobalNamingResources> in server.xml"
xmlstarlet ed -P -S -L -s /Server/GlobalNamingResources -t elem -n ResourceTMP -v "" \
-i //ResourceTMP -t attr -n "name" -v "jdbc/templateassets" \
-i //ResourceTMP -t attr -n "auth" -v "Container" \
-i //ResourceTMP -t attr -n "type" -v "javax.sql.DataSource" \
-i //ResourceTMP -t attr -n "driverClassName" -v "com.mysql.jdbc.Driver" \
-i //ResourceTMP -t attr -n "url" -v "jdbc:mysql://DBHOST:DBPORT/DBNAME?createDatabaseIfNotExist=false&useUnicode=true&characterEncoding=utf-8" \
-i //ResourceTMP -t attr -n "username" -v "DBUSER" \
-i //ResourceTMP -t attr -n "password" -v "DBPASS" \
-i //ResourceTMP -t attr -n "maxActive" -v "150" \
-i //ResourceTMP -t attr -n "maxIdle" -v "10" \
-i //ResourceTMP -t attr -n "initialSize" -v "10" \
-i //ResourceTMP -t attr -n "validationQuery" -v "SELECT 1" \
-i //ResourceTMP -t attr -n "testOnBorrow" -v "true" \
-r //ResourceTMP -v Resource \
server.xml
fi
Хитрость заключается в том, чтобы временно дать уникальное имя новому элементу, чтобы его можно было найти позже с помощью выражения XPATH. После добавления всех атрибутов имя снова изменяется на Resource (с помощью -r).
Значение других параметров xmlstarlet:
-P (or --pf) - preserve original formatting
-S (or --ps) - preserve non-significant spaces
-L (or --inplace) - edit file inplace
Начиная с версии 1.4.0 XMLStarlet (от 2012-08-26), вы можете использовать $prev
(или же $xstar:prev
в качестве аргумента -i
, -a
, а также -s
для ссылки на последний вставленный набор узлов. Смотрите примеры в исходном коде XMLStarlet в файлах. doc/xmlstarlet.txt
, examples/ed-backref1
, examples/ed-backref2
, а также examples/ed-backref-delete
, Вам больше не нужно использовать хитрость, вставляя элемент с временным именем элемента, а затем переименовывая его в конце. Пример examples/ed-backref2
особенно полезно показать, как определить переменную, используемую для ссылки на ранее созданную заметку, чтобы вам не приходилось выполнять такие хитрости, как $prev/..
"перемещаться" из узла.
Как упоминалось @npoostavs, правильный ответ - использовать «подузел». Чтобы улучшить ответ с помощью нового «$ prev», вы можете сделать следующее:
xml ed --inplace \
--subnode /config --type elem --name "sub" \
--var new_node '$prev' \
--insert '$new_node' --type attr --name "class" --value "com.foo" \
test.xml
Со следующим пояснением:
--inplace Change the file "test.xml" directly
--subnode Add a new node called "class" below "/config"
--var Assign the newly created node to the variable new_node
Use single quotes to prevent bash replacing the variable
--insert Insert attribute and value to the newly created node
Пример не работал, пока я не завернул <GlobalNamingResources>
в <Server>
элемент.
Я попробовал трюк из Cellux выше., Он работал отлично! Спасибо!! Но форматирование не сохранилось, просто чтобы попробовать, я избавился от опций -P и -S, и проблемы с форматированием исчезли! Я использую CentOS. Может быть, это может кому-то помочь.