Ayrıştırma ve ini dosyasını bash dizi değişkenlerine dönüştürme?


13

Bir ini dosyasını bash dizi değişkenlerine dönüştürmeye çalışıyorum. Örnek ini aşağıdaki gibidir:

[foobar]
session=foo
path=/some/path

[barfoo]
session=bar
path=/some/path

böylece bunlar:

session[foobar]=foo
path[foobar]=/some/path
session[barfoo]=bar

ve bunun gibi.

Şu anda, sadece bu komutu bulabilirim

awk -F'=' '{ if ($1 ~ /^\[/) section=$1; else if ($1 !~ /^$/) print $1 section "=" $2 }'

Ayrıca, başka bir sorun, boşlukları =dikkate almamasıdır . sedBu iş için muhtemelen daha uygun olduğunu düşünüyorum , ancak bölüm adı için geçici bir değişkenin nasıl tutulacağını ve saklanacağını bilmiyorum sed.

Peki bunu nasıl yapacağınız hakkında bir fikriniz var mı?


Bunu yapmanın başka etkili bir yolu varsa, çözümünüzü de yayınlamaktan çekinmeyin :)
Flint


Basit bir çözüm için şunları kontrol edin: Bir kabuk betiği içindeki bir INI değerini nasıl alabilirim? stackoverflow SE'de.
kenorb

Yanıtlar:


10

Gawk düzenli ifadeleri alan sınırlayıcıları olarak kabul eder. Aşağıdakiler eşittir işaretinin etrafındaki boşlukları ortadan kaldırır, ancak onları çizginin geri kalanında korur. Tırnaklar değerin etrafına eklenir, böylece Bash ataması gerçekleştirildiğinde bu boşluklar (varsa) korunur. Bölüm adlarının sayısal değişkenler olacağını varsayıyorum, ancak Bash 4 kullanıyorsanız, bölüm adları ile ilişkilendirilebilir dizileri endeks olarak kullanmak için bunu uyarlamak kolay olurdu.

awk -F ' *= *' '{ if ($1 ~ /^\[/) section=$1; else if ($1 !~ /^$/) print $1 section "=" "\"" $2 "\"" }'

Bash değişken adları boşluk içeremediğinden, Khaled'in (yalnızca 1 $ ve bölümde) gösterdiği alan kaldırma işlemini de yapmak isteyebileceğinizi unutmayın.

Ayrıca, değerler eşit işaretler içeriyorsa bu yöntem çalışmaz.

Başka bir teknik, bir Bash while readdöngüsü kullanmak ve dosya okunurken ödevleri yapmaktır, declarebu da çoğu kötü amaçlı içerikten güvenlidir.

foobar=1
barfoo=2  # or you could increment an index variable each time a section is found
while IFS='= ' read var val
do
    if [[ $var == \[*] ]]
    then
        section=$var
    elif [[ $val ]]
    then
        declare "$var$section=$val"
    fi
done < filename

Yine, ilişkisel diziler oldukça kolay bir şekilde desteklenebilir.


1
Çok güzel bilgi ve ben özellikle ikinci tekniği seviyorum, çünkü harici komuta dayanmak yerine bash fonksiyonunu kullanır.
Flint

@TonyBarganski: Biri diğerine bağlamak yerine bir AWK çağrısına değiştirilebilir.
sonraki duyuruya kadar duraklatıldı.

10

INI ayrıştırıcı yerleşik beri bu iş için basit python komut dosyası kullanırsınız :

#!/usr/bin/env python

import sys, ConfigParser

config = ConfigParser.ConfigParser()
config.readfp(sys.stdin)

for sec in config.sections():
    print "declare -A %s" % (sec)
    for key, val in config.items(sec):
        print '%s[%s]="%s"' % (sec, key, val)

ve sonra bash:

#!/bin/bash

# load the in.ini INI file to current BASH - quoted to preserve line breaks
eval "$(cat in.ini  | ./ini2arr.py)"

# test it:
echo ${barfoo[session]}

Elbette, awk'de daha kısa uygulamalar var, ancak bunun daha okunabilir ve bakımı daha kolay olduğunu düşünüyorum.


3
4.2'den önceki bash versiyonlarında, bir dolum dizisini doldurmadan önce bildirmek gerekir, örn.print "declare -A %s" % (sec)
Felix Eve

2
Bunun yerine eval:source <(cat in.ini | ./ini2arr.py)
sonraki duyuruya kadar duraklatıldı.

3

Fazla boşlukları ortadan kaldırmak istiyorsanız, yerleşik işlevi kullanabilirsiniz gsub. Örneğin, şunları ekleyebilirsiniz:

gsub(/ /, "", $1);

Bu, tüm alanları kaldıracaktır. Jetonun başındaki veya sonundaki boşlukları kaldırmak istiyorsanız,

gsub(/^ /, "", $1);
gsub(/ $/, "", $1);

Güzel hileler. Böyle yerleşik bir fonksiyon olduğunu bilmiyordum :)
Flint

0

İşte saf bir bash çözümü.

Bu, chilladx'in yayınladığı yeni ve geliştirilmiş bir sürümdür:

https://github.com/albfan/bash-ini-parser

İzlemesi gerçekten kolay ilk örnek için: Bunu indirdikten sonra, dosyaları bash-ini-parserve scripts/file.iniaynı dizine kopyalayın , ardından aynı dizine aşağıda sağladığım örneği kullanarak bir istemci test komut dosyası oluşturun.

source ./bash-ini-parser
cfg_parser "./file.ini"
cfg_section_sec2
echo "var2=$var2"
echo "var5[*]=${var5[*]}"
echo "var5[1]=${var5[1]}"

İşte bash-ini-ayrıştırıcı komut dosyasında yaptığım bazı iyileştirmeler ...

Unix'in yanı sıra Windows satır sonlarına sahip ini dosyalarını okuyabilmek istiyorsanız, bu satırı dosyayı okuyanın hemen ardından cfg_parser işlevine ekleyin:

ini=$(echo "$ini"|tr -d '\r') # remove carriage returns

Kısıtlayıcı erişim izinlerine sahip dosyaları okumak istiyorsanız, bu isteğe bağlı işlevi ekleyin:

# Enable the cfg_parser to read "locked" files
function sudo_cfg_parser {

    # Get the file argument
    file=$1

    # If not "root", enable the "sudo" prefix
    sudoPrefix=
    if [[ $EUID -ne 0 ]]; then sudoPrefix=sudo; fi

    # Save the file permissions, then "unlock" the file
    saved_permissions=$($sudoPrefix stat -c %a $file)
    $sudoPrefix chmod 777 $file

    # Call the standard cfg_parser function
    cfg_parser $file

    # Restore the original permissions
    $sudoPrefix chmod $saved_permissions $file  
}

Çünkü aşağı itmek zorunda kaldı chmod 777. En iyi gölgeli bir uygulama olsa da, ini dosyasını yürütülebilir yapmanıza gerek yoktur. Daha iyi bir yaklaşım sudo, izinleri karıştırmak için değil, dosyayı okumak için kullanılır .
Richlv

@Richlv Ok. Aşağı oy açıklamasını takdir ediyorum. Ancak, bunun bir bütün olarak soruyu cevaplamak kadar asgari öneme sahip küçük bir kısmı. "Cevap" bağlantıdır: github.com/albfan/bash-ini-parser . Aşağıdakilere oy vermek yerine, zaten isteğe bağlı bir sarmalayıcı işlevini etiketleyen şey için bir düzenleme önerebilirdiniz.
BuvinJ19

0

Her zaman Python'un ConfigParser'ının olduğu varsayılırsa, böyle bir kabuk yardımcı işlevi oluşturulabilir:

get_network_value()
{
    cat <<EOF | python
import ConfigParser
config = ConfigParser.ConfigParser()
config.read('network.ini')
print (config.get('$IFACE','$param'))
EOF
}

$IFACEve $paramparametre sırasıyla parametredir.

Bu yardımcı daha sonra aşağıdaki gibi çağrılara izin verir:

address=`param=address get_network_value` || exit 1
netmask=`param=netmask get_network_value` || exit 1
gateway=`param=gateway get_network_value` || exit 1

Bu yardımcı olur umarım!


0

Git'iniz varsa ve anahtar adlarında alt çizgi kullanamama kısıtlaması ile sorun yaşıyorsanız, git configgenel amaçlı bir INI ayrıştırıcısı / düzenleyicisi olarak kullanabilirsiniz.

Anahtar / değer çiftini ayrıştıracak =ve önemsiz boşlukları kaldıracak , ayrıca (hem ;ve hem de #) yorum alıyorsunuz ve temel olarak ücretsiz olarak baskı yazıyorsunuz . .iniAşağıda OP'nin girişi ve istenen çıkışı (Bash ilişkisel dizileri) için tam bir çalışma örneği ekledim.

Ancak, böyle bir yapılandırma dosyası verildiğinde

; mytool.ini
[section1]
    inputdir = ~/some/dir
    enablesomefeature = true
    enablesomeotherfeature = yes
    greeting = Bonjour, Monde!

[section2]
    anothersetting = 42

… Sadece hızlı ve kirli bir çözüme ihtiyacınız varsa ve bir Bash ilişkisel dizisindeki ayarlara sahip olma fikriyle evli değilseniz, şu kadar az şeyden kurtulabilirsiniz:

eval $(git config -f mytool.ini --list | tr . _)

# or if 'eval' skeeves you out excessively
source <(git config -f mytool.ini --list | tr . _)

sectionname_variablenamemevcut ortamda adlandırılan ortam değişkenlerini oluşturur . Bu, elbette, yalnızca değerlerinizden hiçbirinin bir nokta veya boşluk içermeyeceğine güveniyorsanız çalışır (daha sağlam bir çözüm için aşağıya bakın).

Diğer basit örnekler

Yazmayı kaydetmek için kabuk işlevi kullanarak rasgele değerler getiriliyor:

function myini() { git config -f mytool.ini; }

Bash man sayfasına göre, bir takma ad da tamam olurdu, ancak normal olarak bir kabuk komut dosyasında [ 1 ] genişletilmez ve yine de takma adların yerine "hemen hemen her amaç için" [ 2 ] kabuk işlevleri geçer .

myini --list
# result:
# section1.inputdir=~/some/dir
# section1.enablesomefeature=true
# section1.enablesomeotherfeature=yes
# section2.anothersetting=42

myini --get section1.inputdir
# result:
# ~/some/dir

İle --typeseçeneği, sen tamsayılar, Boolean, ya yollar (otomatik genişleyen olarak can "canonicalize" özel ayarlar ~):

myini --get --type=path section1.inputdir  # value '~/some/dir'
# result:
# /home/myuser/some/dir

myini --get --type=bool section1.enablesomeotherfeature  # value 'yes'
# result:
# true

Biraz daha sağlam çabuk ve kirli örnek

Tüm değişkenleri geçerli ortamdaki mytool.inigibi kullanılabilir yapın SECTIONNAME_VARIABLENAMEve anahtar boşluklarda dahili boşlukları koruyun:

source <(
    git config -f mytool.ini --list \
      | sed 's/\([^.]*\)\.\(.*\)=\(.*\)/\U\1_\2\E="\3"/'
)

Sed ifadesinin İngilizce yaptığı şey,

  1. bir döneme kadar bir dizi dönem olmayan karakter bulmak, bunu hatırlamak \1,
  2. eşit sayıda işarete kadar bir grup karakter bulmak, bunu hatırlamak \2ve
  3. eşittir işaretinden sonra tüm karakterleri bulma \3
  4. son olarak, yedek dizede
    • bölüm adı + değişken adı üst kasalı ve
    • eğer tırnak işareti belirtilmemişse kabuk için özel bir anlamı olan karakterler içermesi durumunda (boşluk gibi) değer bölümü çift tırnak içine alınır

\UVe \Edeğiştirme dizesi diziler (ki değiştirme dizisinin parçası, üst durum) GNU olan seduzantı. MacOS ve BSD'de -eaynı efekti elde etmek için yalnızca birden fazla ifade kullanırsınız.

Bölüm adlarında ( git configizin veren) gömülü tırnak işaretleri ve boşluklarla ilgilenmek okuyucu için bir alıştırma olarak bırakılmıştır.:)

Bölüm adlarını Bash ilişkilendirilebilir dizisine anahtar olarak kullanma

Verilen:

; foo.ini
[foobar]
session=foo
path=/some/path

[barfoo]
session=bar
path=/some/path

Bu, OP'nin istediği sonucu, sadece sed değiştirme ifadesindeki bazı yakalamaları yeniden düzenleyerek üretecek ve GNU sed olmadan iyi çalışacaktır:

source <(
    git config -f foo.ini --list \
      | sed 's/\([^.]*\)\.\(.*\)=\(.*\)/declare -A \2["\1"]="\3"/'
)

Gerçek dünya .inidosyası için alıntı yaparken bazı zorluklar olabileceğini tahmin ediyorum , ancak sağlanan örnek için çalışıyor. Sonuç:

declare -p {session,path}
# result:
# declare -A session=([barfoo]="bar" [foobar]="foo" )
# declare -A path=([barfoo]="/some/path" [foobar]="/some/path" )
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.