對於設定檔,越來越不喜歡手工用 vi / vim 指令調整。
而是傾向先預備好,並代替成對等 sed 指令做編輯取代。
對於普通規格設定檔(等號填寫內容),這種替代搭配 regex 很合用。
但對於半結構化的檔案:XML、JSON、YAML,sed 的替代錯誤的風險會便很高。
這邊筆記的是針對 XML 的指令式編輯取代的標準手法。
而是傾向先預備好,並代替成對等 sed 指令做編輯取代。
對於普通規格設定檔(等號填寫內容),這種替代搭配 regex 很合用。
但對於半結構化的檔案:XML、JSON、YAML,sed 的替代錯誤的風險會便很高。
這邊筆記的是針對 XML 的指令式編輯取代的標準手法。
半結構化格式的設定檔困擾的是,修飾符號很多,以及不能單純使用 regex 編輯取代,因為更多狀況是要用搜尋條件批配上游/下游/左右鄰兵,來鎖定要編輯的內容。
對於搜尋條件的語法,XML 稱作 XPath,JSON 稱作 JSON Path。
另外還有樣板描述(metadata),XML 的稱作 XSLT,而 JSON 的則是 JSON Schema。
對於 XML,可以用 XPath 處理的指令就是 xmllint 與 xmlstarlet 兩種指令。
xmllint 指令在 RHEL 系列可以直接透過 yum/dnf 指令安裝,而 xmlstarlet 也能以 yum/dnf 安裝,但需要從 EPEL Repo 取得。
這邊筆記的是 xmllint,平常比較容易快速取得。在 RHEL 系列應該只要從 OS 預設 Repo 裝 libxml2 套件即可。
bash-5.1# dnf provides xmllint Last metadata expiration check: 2:26:00 ago on Fri 05 Jan 2024 04:14:09 AM UTC. libxml2-2.9.13-1.el9.i686 : Library providing XML and HTML support Repo : baseos Matched from: Filename : /usr/bin/xmllint libxml2-2.9.13-1.el9.x86_64 : Library providing XML and HTML support Repo : baseos Matched from: Filename : /usr/bin/xmllint libxml2-2.9.13-2.el9.i686 : Library providing XML and HTML support Repo : baseos Matched from: Filename : /usr/bin/xmllint libxml2-2.9.13-2.el9.x86_64 : Library providing XML and HTML support Repo : baseos Matched from: Filename : /usr/bin/xmllint libxml2-2.9.13-3.el9.i686 : Library providing XML and HTML support Repo : baseos Matched from: Filename : /usr/bin/xmllint libxml2-2.9.13-3.el9.x86_64 : Library providing XML and HTML support Repo : baseos Matched from: Filename : /usr/bin/xmllint libxml2-2.9.13-4.el9.i686 : Library providing XML and HTML support Repo : baseos Matched from: Filename : /usr/bin/xmllint libxml2-2.9.13-4.el9.x86_64 : Library providing XML and HTML support Repo : baseos Matched from: Filename : /usr/bin/xmllint libxml2-2.9.13-5.el9.i686 : Library providing XML and HTML support Repo : baseos Matched from: Filename : /usr/bin/xmllint libxml2-2.9.13-5.el9.x86_64 : Library providing XML and HTML support Repo : @System Matched from: Filename : /usr/bin/xmllint libxml2-2.9.13-5.el9.x86_64 : Library providing XML and HTML support Repo : baseos Matched from: Filename : /usr/bin/xmllint bash-5.1# dnf install -y libxml2 ...略
幾個要緊的 XPath 資訊(其他還有一些,可以後續再去了解)
- XPath 類比 Linux 目錄路徑,將巢狀的 XML 用路徑表示指向具體位置
- XML 的值藏在 tag 之間,以及 tag 的角括號內。角括號內藏的稱作 attribute
- XML tag 通常成對,中間夾一個值,可以直接以路徑指到底找到
- XML attribute 屬於 key/value 形式,能以 @ 記號抓 key 以顯示 value
xmllint 通常是用於「顯示」XPath 查詢的指令,不過 xmllint 特別提供互動式查找&編輯取代,因此利用 xmllint 代替 sed 對 XML 編輯的方式,就是以互動模式搭配 shell 的 heredoc 內容導向,把互動模式中的「指令」透過 pipe 塞給 xmllint 執行。
以下為兩個範例
- 一個 Log4J2 的原始 XML 設定檔
這個範例是要簡單替換一個 attribute 的值,這個也適用變更 tag 路徑的值
https://github.com/pentaho/pentaho-kettle/blob/master/assemblies/core/static/src/main/resources-standard/classes/log4j2.xml - 一個 Tomcat 的 META-INF/context.xml 設定檔(算是 Tomcat webapps 的資料庫連線設定檔)
這個會搭配條件,透過 XPath 的 start-with() 函數定位,在替換對應的 attribute 的值
https://github.com/pentaho/pentaho-platform/blob/master/assemblies/pentaho-war/src/main/resources/datasources/postgresql/context.xml - 修正 Maven 編譯的 pom.xml 設定檔
這邊有 XML namespace 要指定,只用 XPath 會找不到
https://github.com/strimzi/mirror-maker-2-extensions/blob/1.1.0/pom.xml
第一個範例:
抓檔案下來後,首先先熟悉一下 XPath 路徑表示
bash-5.1$ curl https://raw.githubusercontent.com/pentaho/pentaho-kettle/10.1.0.0-175/assemblies/core/static/src/main/resources-standard/classes/log4j2.xml -o log4j2.xml
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 4636 100 4636 0 0 8681 0 --:--:-- --:--:-- --:--:-- 8697
bash-5.1$ xmllint --xpath '/Configuration/Appenders/RollingFile' ./log4j2.xml
<RollingFile name="pdi-execution-appender" fileName="logs/pdi.log" filePattern="logs/pdi.%d{yyyy-MM-dd}.log">
<PatternLayout>
<Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5p <%t> %m%n</Pattern>
</PatternLayout>
<Policies>
<TimeBasedTriggeringPolicy/>
</Policies>
<DefaultRolloverStrategy/>
</RollingFile>
bash-5.1$ xmllint --xpath '/Configuration/Appenders/RollingFile/@fileName' ./log4j2.xml
fileName="logs/pdi.log"
bash-5.1$
bash-5.1$ xmllint --xpath 'string(//Configuration/Appenders/RollingFile/@fileName)' ./log4j2.xml
logs/pdi.log
bash-5.1$
接著是進入互動式 shell 操作確認,這個模式底下,就能夠編輯檔案&存檔了。
bash-5.1$ xmllint --shell ./log4j2.xml
/ > help
base display XML base of the node
setbase URI change the XML base of the node
bye leave shell
cat [node] display node or current node
cd [path] change directory to path or to root
dir [path] dumps information about the node (namespace, attributes, content)
du [path] show the structure of the subtree under path or the current node
exit leave shell
help display this help
free display memory usage
load [name] load a new document with name
ls [path] list contents of path or the current directory
set xml_fragment replace the current node content with the fragment parsed in context
xpath expr evaluate the XPath expression in that context and print the result
setns nsreg register a namespace to a prefix in the XPath evaluation context
format for nsreg is: prefix=[nsuri] (i.e. prefix= unsets a prefix)
setrootns register all namespace found on the root element
the default namespace if any uses 'defaultns' prefix
pwd display current working directory
whereis display absolute path of [path] or current working directory
quit leave shell
save [name] save this document to name or the original name
write [name] write the current node to the filename
validate check the document for errors
relaxng rng validate the document against the Relax-NG schemas
grep string search for a string in the subtree
/ > cd //Configuration/Appenders/RollingFile/@fileName
fileName > cat
fileName="logs/pdi.log"
fileName > set "/tmp/pdi.log"
fileName > cat
fileName=""/tmp/pdi.log""
fileName > set /tmp/pdi.log
fileName > cat
fileName="/tmp/pdi.log"
fileName >
fileName > quit
bash-5.1$
最後把上面操作做成 heredoc,以 pipe 的方式執行
bash-5.1$ export KETTLE_HOME=/opt/pentaho/ bash-5.1$ xmllint --shell ./log4j2.xml << EOF cd //Configuration/Appenders/RollingFile/@fileName set $KETTLE_HOME/logs/pdi.log save EOF bash-5.1$
第二個範例:
這個其實跟上一個差不了多少,不過這邊以類比 where 條件的方式(starts-with 函數),定位所在的 XML 節點,以正確替換內容。
抓檔案下來後,就直接用互動式 shell 了~互動式 shell 可以預覽編輯,跟 vim 一樣,不要儲存就不會生效。不過我這邊還是儲存一下~
因為這份 XML 的樣貌是典型的相似內容疊在一起的狀況,這邊保留一點碰壁的操作。
bash-5.1$ curl https://raw.githubusercontent.com/pentaho/pentaho-platform/10.1.0.0-175/assemblies/pentaho-war/src/main/resources/datasources/postgresql/context.xml -o context.xml % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 1350 100 1350 0 0 2132 0 --:--:-- --:--:-- --:--:-- 2132 bash-5.1$ bash-5.1$ xmllint --shell ./context.xml / > cd //Context/Resource //Context/Resource is a 3 Node Set / > / > cd //Context/Resource[start-with(@name, "jdbc/Hibernate")]/username xmlXPathCompOpEval: function start-with not found XPath error : Unregistered function //Context/Resource[start-with(@name, "jdbc/Hibernate")]/username: no such node / > / > cd //Context/Resource[starts-with(@name, "jdbc/Hibernate")]/username //Context/Resource[starts-with(@name, "jdbc/Hibernate")]/username is a 0 Node Set / > / > cd //Context/Resource[starts-with(@name, "jdbc/Hibernate")]/@username username > cat username="hibuser" username > cd //Context/Resource[starts-with(@name, "jdbc/Hibernate")]/@password password > cat password="password" password > password > set Encrypted 2XXXXXXX86aa7f2e4bb18XXXXXX9dbdde password > cat password="Encrypted 2XXXXXXX86aa7f2e4bb18XXXXXX9dbdde" password > password > save password > quit bash-5.1$ bash-5.1$ cat ./context.xml <?xml version="1.0" encoding="UTF-8"?> <Context path="/pentaho" docbase="webapps/pentaho/"> <Resource name="jdbc/Hibernate" auth="Container" type="javax.sql.DataSource" factory="org.pentaho.di.core.database.util.DecryptingDataSourceFactory" maxActive="20" minIdle="0" maxIdle="5" initialSize="0" maxWait="10000" username="hibuser" password="Encrypted 2XXXXXXX86aa7f2e4bb18XXXXXX9dbdde" driverClassName="org.postgresql.Driver" url="jdbc:postgresql://localhost:5432/hibernate" validationQuery="select 1" jdbcInterceptors="ConnectionState" defaultAutoCommit="true"/> <Resource name="jdbc/Quartz" auth="Container" type="javax.sql.DataSource" factory="org.pentaho.di.core.database.util.DecryptingDataSourceFactory" maxActive="20" minIdle="0" maxIdle="5" initialSize="0" maxWait="10000" username="pentaho_user" password="password" testOnBorrow="true" driverClassName="org.postgresql.Driver" url="jdbc:postgresql://localhost:5432/quartz" validationQuery="select 1"/> <Resource name="jdbc/jackrabbit" auth="Container" type="javax.sql.DataSource" factory="org.pentaho.di.core.database.util.DecryptingDataSourceFactory" maxActive="20" minIdle="0" maxIdle="5" initialSize="0" maxWait="10000" username="jcr_user" password="password" driverClassName="org.postgresql.Driver" url="jdbc:postgresql://localhost:5432/jackrabbit" validationQuery="select 1" jdbcInterceptors="ConnectionState" defaultAutoCommit="true"/> </Context> bash-5.1$
一樣,可以把上面的指令操作匯總成 script,以 heredoc 執行
bash-5.1$ xmllint --shell $KETTLE_HOME/pentaho-server/tomcat/webapps/pentaho/META-INF/context.xml << EOSHL
cd //Context/Resource[starts-with(@name, "jdbc/Quartz")]/@username
set Encrypted 2XXXXXXX86aa7f2e4bb18XXXXXX9dbdde
cd //Context/Resource[starts-with(@name, "jdbc/jackrabbit")]/@username
set Encrypted 2XXXXXXX86aa7f2e4bb18XXXXXX9dbdde
save
quit
EOSHL
bash-5.1$
第三個範例:
通常 Maven 有相依性,會寫在 pom.xml 檔案內,有時會希望更新相依性。
這邊的操作範例要把 Apache Kafka 版本更正。
bash-5.1$ xmllint --xpath "/*[local-name()='project' and namespace-uri()='http://maven.apache.org/POM/4.0.0']/*[local-name()='groupId']" ./mirror-maker-2-extensions-1.1.0/pom.xml
<groupId>io.strimzi</groupId>
bash-5.1$
bash-5.1$ xmllint --xpath "/*[local-name()='project' and namespace-uri()='http://maven.apache.org/POM/4.0.0']/*[local-name()='dependencies']/*[local-name()='dependency']/*[local-name()='groupId']" ./mirror-maker-2-extensions-1.1.0/pom.xml
<groupId>org.apache.kafka</groupId>
<groupId>org.apache.kafka</groupId>
<groupId>org.slf4j</groupId>
bash-5.1$ xmllint --shell pom.xml << EOCMD
setns ns=http://maven.apache.org/POM/4.0.0
cat /ns:project/ns:dependencies/ns:dependency/ns:groupId/text()
EOCMD
/ > / > -------
org.apache.kafka
-------
org.apache.kafka
-------
org.slf4j
/ > bash-5.1$
bash-5.1$ xmllint --shell ./mirror-maker-2-extensions-1.1.0/pom.xml << EOCMD
setns ns=http://maven.apache.org/POM/4.0.0
cat /ns:project/ns:dependencies/ns:dependency/ns:groupId
EOCMD
/ > / > -------
<groupId>org.apache.kafka</groupId>
-------
<groupId>org.apache.kafka</groupId>
-------
<groupId>org.slf4j</groupId>
/ > bash-5.1$
bash-5.1$
bash-5.1$ xmllint --shell ./mirror-maker-2-extensions-1.1.0/pom.xml << EOCMD
setns ns=http://maven.apache.org/POM/4.0.0
cat /ns:project/ns:properties/ns:kafka.version
quit
EOCMD
/ > / > -------
<kafka.version>3.0.0</kafka.version>
/ > bash-5.1$
bash-5.1$
bash-5.1$ xmllint --shell ./mirror-maker-2-extensions-1.1.0/pom.xml << EOCMD
setns ns=http://maven.apache.org/POM/4.0.0
cd /ns:project/ns:properties/ns:kafka.version
set 2.6.0
cd /ns:project/ns:properties/ns:maven.compiler.version
set 3.6.2
cd /ns:project/ns:properties/ns:maven.compiler.source
set 11
cd /ns:project/ns:properties/ns:maven.compiler.target
set 11
save
quit
EOCMD
/ > / > kafka.version > kafka.version > maven.compiler.version > maven.compiler.version > maven.compiler.source > maven.compiler.source > maven.compiler.target > maven.compiler.target > maven.compiler.target > bash-5.1$
bash-5.1$
bash-5.1$ xmllint --shell ./mirror-maker-2-extensions-1.1.0/pom.xml << EOCMD
setns ns=http://maven.apache.org/POM/4.0.0
cat /ns:project/ns:properties/ns:kafka.version
quit
EOCMD
/ > / > -------
<kafka.version>2.6.0</kafka.version>
/ > bash-5.1$
bash-5.1$
有了這個對比 sed 的手法,湊小抄就更便利了~
之後在看看 jq 如何對 JSON 達成相似的處理~
參考資料
類比 where 條件的方式(starts-with 函數)
XPath 語法
JSON 的對等指令 jq 也有 startwith() 語法
XML namespace 的指定
沒有留言:
張貼留言