Phân tích cú pháp XML để lấy giá trị nút trong tập lệnh bash?


19

Tôi muốn biết làm thế nào tôi có thể nhận được giá trị của một nút với các đường dẫn sau:

config/global/resources/default_setup/connection/host
config/global/resources/default_setup/connection/username
config/global/resources/default_setup/connection/password
config/global/resources/default_setup/connection/dbname

từ XML sau:

<?xml version="1.0"?>
<config>
    <global>
        <install>
            <date><![CDATA[Tue, 11 Dec 2012 12:31:25 +0000]]></date>
        </install>
        <crypt>
            <key><![CDATA[70e75d7969b900b696785f2f81ecb430]]></key>
        </crypt>
        <disable_local_modules>false</disable_local_modules>
        <resources>
            <db>
                <table_prefix><![CDATA[]]></table_prefix>
            </db>
            <default_setup>
                <connection>
                    <host><![CDATA[localhost]]></host>
                    <username><![CDATA[root]]></username>
                    <password><![CDATA[pass123]]></password>
                    <dbname><![CDATA[testdb]]></dbname>
                    <initStatements><![CDATA[SET NAMES utf8]]></initStatements>
                    <model><![CDATA[mysql4]]></model>
                    <type><![CDATA[pdo_mysql]]></type>
                    <pdoType><![CDATA[]]></pdoType>
                    <active>1</active>
                </connection>
            </default_setup>
        </resources>
        <session_save><![CDATA[files]]></session_save>
    </global>
    <admin>
        <routers>
            <adminhtml>
                <args>
                    <frontName><![CDATA[admin]]></frontName>
                </args>
            </adminhtml>
        </routers>
    </admin>
</config>

Ngoài ra tôi muốn gán giá trị đó cho biến để sử dụng tiếp. Hãy cho tôi biết ý tưởng của bạn.


7
Đừng bao giờ sử dụng bash để phân tích các cây có cấu trúc dữ liệu tùy ý. Sử dụng một trình phân tích cú pháp XML thực sự. Tôi khuyên dùng XMLStarlet .
Chris Xuống

Câu trả lời:


19

Sử dụng bashxmllint(như được đưa ra bởi các thẻ):

xmllint --version  #  xmllint: using libxml version 20703

# Note: Newer versions of libxml / xmllint have a --xpath option which 
# makes it possible to use xpath expressions directly as arguments. 
# --xpath also enables precise output in contrast to the --shell & sed approaches below.
#xmllint --help 2>&1 | grep -i 'xpath'

{
# the given XML is in file.xml
host="$(echo "cat /config/global/resources/default_setup/connection/host/text()" | xmllint --nocdata --shell file.xml | sed '1d;$d')"
username="$(echo "cat /config/global/resources/default_setup/connection/username/text()" | xmllint --nocdata --shell file.xml | sed '1d;$d')"
password="$(echo "cat /config/global/resources/default_setup/connection/password/text()" | xmllint --nocdata --shell file.xml | sed '1d;$d')"
dbname="$(echo "cat /config/global/resources/default_setup/connection/dbname/text()" | xmllint --nocdata --shell file.xml | sed '1d;$d')"
printf '%s\n' "host: $host" "username: $username" "password: $password" "dbname: $dbname"
}

# output
# host: localhost
# username: root
# password: pass123
# dbname: testdb

Trong trường hợp chỉ có một chuỗi XML và cần tránh sử dụng một tệp tạm thời, các mô tả tệp là cách để đi cùng xmllint(được đưa ra /dev/fd/3dưới dạng đối số tệp ở đây):

set +H
{
xmlstr='<?xml version="1.0"?>
<config>
    <global>
        <install>
            <date><![CDATA[Tue, 11 Dec 2012 12:31:25 +0000]]></date>
        </install>
        <crypt>
            <key><![CDATA[70e75d7969b900b696785f2f81ecb430]]></key>
        </crypt>
        <disable_local_modules>false</disable_local_modules>
        <resources>
            <db>
                <table_prefix><![CDATA[]]></table_prefix>
            </db>
            <default_setup>
                <connection>
                    <host><![CDATA[localhost]]></host>
                    <username><![CDATA[root]]></username>
                    <password><![CDATA[pass123]]></password>
                    <dbname><![CDATA[testdb]]></dbname>
                    <initStatements><![CDATA[SET NAMES utf8]]></initStatements>
                    <model><![CDATA[mysql4]]></model>
                    <type><![CDATA[pdo_mysql]]></type>
                    <pdoType><![CDATA[]]></pdoType>
                    <active>1</active>
                </connection>
            </default_setup>
        </resources>
        <session_save><![CDATA[files]]></session_save>
    </global>
    <admin>
        <routers>
            <adminhtml>
                <args>
                    <frontName><![CDATA[admin]]></frontName>
                </args>
            </adminhtml>
        </routers>
    </admin>
</config>
'

# exec issue
#exec 3<&- 3<<<"$xmlstr"
#exec 3<&- 3< <(printf '%s' "$xmlstr")
exec 3<&- 3<<EOF
$(printf '%s' "$xmlstr")
EOF

{ read -r host; read -r username; read -r password; read -r dbname; } < <(
       echo "cat /config/global/resources/default_setup/connection/*[self::host or self::username or self::password or self::dbname]/text()" | 
          xmllint --nocdata --shell /dev/fd/3 | 
          sed -e '1d;$d' -e '/^ *--* *$/d'
       )

printf '%s\n' "host: $host" "username: $username" "password: $password" "dbname: $dbname"

exec 3<&-
}
set -H


# output
# host: localhost
# username: root
# password: pass123
# dbname: testdb


6

Mặc dù đã có rất nhiều câu trả lời, tôi sẽ đồng ý xml2.

$ xml2 < test.xml
/config/global/install/date=Tue, 11 Dec 2012 12:31:25 +0000
/config/global/crypt/key=70e75d7969b900b696785f2f81ecb430
/config/global/disable_local_modules=false
/config/global/resources/db/table_prefix
/config/global/resources/default_setup/connection/host=localhost
/config/global/resources/default_setup/connection/username=root
/config/global/resources/default_setup/connection/password=pass123
/config/global/resources/default_setup/connection/dbname=testdb
/config/global/resources/default_setup/connection/initStatements=SET NAMES utf8
/config/global/resources/default_setup/connection/model=mysql4
/config/global/resources/default_setup/connection/type=pdo_mysql
/config/global/resources/default_setup/connection/pdoType
/config/global/resources/default_setup/connection/active=1
/config/global/session_save=files
/config/admin/routers/adminhtml/args/frontName=admin

Với một chút phép thuật, bạn thậm chí có thể đặt chúng làm biến trực tiếp:

$ eval $(xml2 < test.xml | tr '/, ' '___' | grep =)
$ echo $_config_global_resources_default_setup_connection_host          
localhost

3

Các hoạt động sau đây khi chạy với dữ liệu thử nghiệm của bạn:

{ read -r host; read -r username; read -r password; read -r dbname; } \
  < <(xmlstarlet sel -t -m /config/global/resources/default_setup/connection \
      -v ./host -n \
      -v ./username -n \
      -v ./password -n \
      -v ./dbname -n)

Điều này đặt các nội dung vào các biến host, username, passworddbname.


xmlstarlet: không tìm thấy lệnh, vì vậy lệnh này không hữu ích với tôi :(
MagePologistso 17/07/13

@MagePologistso bashkhông có bất kỳ hỗ trợ tích hợp nào cho phân tích cú pháp XML. Bạn cần phải có một công cụ thực hiện (xmlstarlet, xsltproc, Python hiện đại, v.v.) hoặc bạn không thể phân tích cú pháp XML chính xác.
Charles Duffy

@CharlesDuffy có cách nào để lấy giá trị có thể sử dụng mẫu regex hay không?
MagePologistso 17/07/13

5
@MagePologistso bạn chỉ cần cài đặt xmlstarlet. Trong mọi trường hợp, bạn không bao giờ nên sử dụng các biểu thức thông thường để phân tích cú pháp (X) HTML .
terdon

1
@MagePologistso Tôi sắp đăng cùng một liên kết terdon đã làm. Tóm lại: Không.
Charles Duffy

3

Một bashchức năng thuần túy , chỉ dành cho trường hợp không may khi bạn không được phép cài đặt bất cứ thứ gì phù hợp. Điều này có thể và có thể sẽ thất bại trên XML phức tạp hơn:

function xmlpath()
{
  local expr="${1//\// }"
  local path=()
  local chunk tag data

  while IFS='' read -r -d '<' chunk; do
    IFS='>' read -r tag data <<< "$chunk"

    case "$tag" in
      '?'*) ;;
      '!–-'*) ;;
      '![CDATA['*) data="${tag:8:${#tag}-10}" ;;
      ?*'/') ;;
      '/'?*) unset path[${#path[@]}-1] ;;
      ?*) path+=("$tag") ;;
    esac

    [[ "${path[@]}" == "$expr" ]] && echo "$data"
  done
}

Sử dụng:

bash-4.1$ xmlpath 'config/global/resources/default_setup/connection/host' < MagePsycho.xml
localhost

Các vấn đề đã biết:

  • chậm
  • chỉ tìm kiếm theo tên thẻ
  • không giải mã thực thể nhân vật

2

Sử dụng xmllint và tùy chọn --xpath , rất dễ dàng. Bạn chỉ có thể làm điều này:

XML_FILE=/path/to/file.xml

HOST=$(xmllint --xpath 'string(/config/global/resources/default_setup/connection/host)' $XML_FILE
USERNAME=$(xmllint --xpath 'string(/config/global/resources/default_setup/connection/username)' $XML_FILE
PASSWORD=$(xmllint --xpath 'string(/config/global/resources/default_setup/connection/password)' $XML_FILE 
DBNAME=$(xmllint --xpath 'string(/config/global/resources/default_setup/connection/dbname)' $XML_FILE

Nếu bạn cần đến thuộc tính của một phần tử, điều đó cũng dễ dàng khi sử dụng XPath. Hãy tưởng tượng bạn có tập tin:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="screensaver.turnoff"
       name="Turn Off"
       version="0.10.0"
       provider-name="Dag Wieërs">
  ..snip..
</addon>

Các báo cáo shell cần thiết sẽ là:

VERSION=$(xmllint --xpath 'string(/addon/@version)' $ADDON_XML)
AUTHOR=$(xmllint --xpath 'string(/addon/@provider-name)' $ADDON_XML)

0

Bạn có thể sử dụng mã hóa giao diện dòng lệnh php trong các tập lệnh bash để xử lý một số tập lệnh phức tạp thực sự trải rộng trên nhiều dòng mã hóa. Trước tiên, hãy thử tạo giải pháp của bạn bằng các tập lệnh PHP và sau đó chuyển các tham số bằng chế độ CLI. Do đó, bạn có thể kiểm soát các ứng dụng tuyệt vời của trình phân tích cú pháp XML.

Môi trường dường như bạn có thể sử dụng PHP trong chế độ máy khách thông qua truy cập ssh / shell.

php -f yourxmlparser.php

Bây giờ, làm tất cả những điều trong tập tin php của bạn. Sử dụng các tham số dòng lệnh nó có thể mất.

Bạn thậm chí có thể gán giá trị trả về đó cho môi trường Shell để tiếp tục phần còn lại của tập lệnh shell.

Và cách khác là sử dụng tùy chọn | grep để khớp với giá trị yêu cầu của bạn trong tệp xml, nếu bạn khá chắc chắn về cấu trúc của tệp xml không thay đổi theo thời gian.


0

Nhận xét này chỉ sử dụng các lệnh và phương thức sh / bash! /test.xml là tệp loại XML của bạn ở câu hỏi đầu tiên ...

#!/bin/sh

cat /test.xml | while read line;do
[ "$(echo "$line" | grep "<host>")" ]&& echo "host: $(echo $line |  cut -f3 -d'[' | cut -f1 -d']')"
[ "$(echo "$line" | grep "<username>")" ]&& echo "username: $(echo $line |  cut -f3 -d'[' | cut -f1 -d']')"
[ "$(echo "$line" | grep "<password>")" ]&& echo "password: $(echo $line |  cut -f3 -d'[' | cut -f1 -d']')"
[ "$(echo "$line" | grep "<dbname")" ]&& echo "dbname: $(echo $line |  cut -f3 -d'[' | cut -f1 -d']')"
done

đầu ra:

host: localhost
username: root
password: pass123
dbname: testdb

Nếu bạn muốn ghi giá trị này vào tệp, hãy sử dụng phương thức này:

#!/bin/sh

cat /test.xml | while read line;do
[ "$(echo "$line" | grep "<host>")" ]&& echo "$line" |  cut -f3 -d'[' | cut -f1 -d']' > /config/global/resources/default_setup/connection/host
[ "$(echo "$line" | grep "<username>")" ]&& echo "$line" |  cut -f3 -d'[' | cut -f1 -d']' > /config/global/resources/default_setup/connection/username
[ "$(echo "$line" | grep "<password>")" ]&& echo "$line" |  cut -f3 -d'[' | cut -f1 -d']' > /config/global/resources/default_setup/connection/password
[ "$(echo "$line" | grep "<dbname")" ]&& echo "$line" |  cut -f3 -d'[' | cut -f1 -d']' > /config/global/resources/default_setup/connection/dbname
done

phương pháp này sẽ ghi đè lên các tệp cục bộ của bạn chỉ được sử dụng để nhận các giá trị (dữ liệu của bạn sẽ bị mất từ ​​các tệp đầu ra)

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.