Analisar XML para obter o valor do nó no script bash?

18

Gostaria de saber como posso obter o valor de um nó com os seguintes caminhos:

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

do seguinte XML:

<?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>

Também quero atribuir esse valor à variável para uso posterior. Deixe-me saber sua ideia.

Mago Psico
fonte
7
Nunca use o bash para analisar árvores estruturadas de dados arbitrários. Use um analisador XML real. Eu recomendo o XMLStarlet .
21313 Chris Down
stackoverflow.com/questions/893585/how-to-parse-xml-in-bash
Ciro Santilli新疆改造中心法轮功六四事件
Lol, Magento demais?
siliconrockstar

Respostas:

19

Usando bashe xmllint(conforme fornecido pelas tags):

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

Caso exista apenas uma string XML e o uso de um arquivo temporário seja evitado, os descritores de arquivos são o caminho a seguir xmllint(que é fornecido /dev/fd/3como argumento de arquivo aqui):

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
Paoul
fonte
1
Página do manual
Jason Pyeron
6

Embora já existam muitas respostas, eu vou conversar 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

Com um pouco de magia, você pode até definir essas variáveis ​​diretamente:

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

O seguinte funciona quando executado nos seus dados de teste:

{ 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)

Isso coloca o conteúdo em variáveis host, username, passworde dbname.

Charles Duffy
fonte
xmlstarlet: comando não encontrado, por isso, este comando não é útil para mim :(
MagePsycho
O @MagePsycho bashnão possui nenhum suporte interno para análise de XML. Você precisa ter uma ferramenta (xmlstarlet, xsltproc, um Python moderno, etc) ou não pode analisar o XML corretamente.
Charles Duffy
@CharlesDuffy, existe uma maneira de obter o valor usando o padrão regex ou então?
MagePsycho
5
@MagePsycho, você pode simplesmente instalar o xmlstarlet. De qualquer forma, você nunca deve usar expressões regulares para analisar o HTML (X) .
terdon
1
@MagePsycho Eu estava prestes a postar o mesmo link que o Terdon já fez. Resumindo: Não.
Charles Duffy
3

Uma bashfunção pura , apenas para o caso lamentável em que você não tem permissão para instalar qualquer coisa apropriada. Isso pode e provavelmente irá falhar em XML mais complicado:

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
}

Uso:

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

Problemas conhecidos:

  • lento
  • pesquisa apenas por nomes de tags
  • nenhuma decodificação de entidade de caractere
homem a trabalhar
fonte
0

Você pode usar a codificação da interface de linha de comando php em scripts bash para lidar com vários scripts complexos que realmente se estendem por várias linhas de codificação. Primeiro, tente criar sua solução usando scripts PHP e depois passe os parâmetros usando o modo CLI. Assim, você pode obter controle sobre os excelentes usos dos analisadores XML.

O ambiente parece que você pode usar o PHP no modo cliente via acesso ssh / shell.

php -f yourxmlparser.php

Agora, faça todas as coisas dentro do seu arquivo php. Faça uso dos parâmetros da linha de comando que podem ser necessários.

Você pode até atribuir esses valores de retorno ao ambiente Shell para continuar o restante de seus scripts shell.

E a outra maneira é usar a opção | grep para corresponder ao valor necessário no arquivo xml, se você tiver certeza da estrutura do seu arquivo xml que não muda com o tempo.

Bimal Poudel
fonte
0

Usando xmllint e a opção --xpath , é muito fácil. Você pode simplesmente fazer isso:

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

Se você precisar acessar o atributo de um elemento, também será fácil usar o XPath. Imagine que você tem o arquivo:

<?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>

As instruções shell necessárias seriam:

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