Bagaimana cara mengurai dan mengonversi file ini menjadi variabel bash array?


12

Saya mencoba mengubah file ini menjadi variabel array bash. Sampel ini adalah sebagai berikut:

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

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

jadi ini menjadi:

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

dan seterusnya.

Saat ini, saya hanya bisa membuat perintah ini

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

Juga, masalah lain adalah, tidak mengambil ruang dekat =menjadi pertimbangan. Saya pikir sedmungkin lebih cocok untuk pekerjaan ini, tetapi saya tidak tahu bagaimana memegang dan menyimpan variabel sementara untuk nama bagian dalam sed.

Jadi, ada ide bagaimana melakukan ini?


Jika ada cara lain yang efisien untuk melakukan ini, jangan ragu untuk mengirim solusi Anda juga :)
Flint


Untuk solusi sederhana, periksa: Bagaimana cara mengambil nilai INI dalam skrip shell? di stackoverflow SE.
kenorb

Jawaban:


10

Gawk menerima ekspresi reguler sebagai pembatas bidang. Berikut ini menghilangkan spasi di sekitar tanda sama dengan, tetapi mempertahankannya di sisa garis. Kutipan ditambahkan di sekitar nilai sehingga ruang-ruang itu, jika ada, dipertahankan ketika tugas Bash dilakukan. Saya berasumsi bahwa nama bagian akan menjadi variabel numerik, tetapi jika Anda menggunakan Bash 4, akan mudah untuk mengadaptasi ini menggunakan array asosiatif dengan nama bagian itu sendiri sebagai indeks.

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

Perhatikan bahwa Anda mungkin ingin juga melakukan penghapusan spasi yang ditampilkan Khaled (hanya $ 1 dan bagian) karena nama variabel Bash tidak boleh mengandung spasi.

Juga, metode ini tidak akan berfungsi jika nilainya mengandung tanda yang sama.

Teknik lain adalah dengan menggunakan Bash while readloop dan melakukan tugas saat file dibaca, menggunakan declareyang aman dari sebagian besar konten berbahaya.

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

Sekali lagi, array asosiatif dapat dengan mudah didukung.


1
Info yang sangat bagus dan saya sangat suka teknik kedua karena menggunakan fungsi bash built in, daripada mengandalkan perintah eksternal.
Flint

@TonyBarganski: Itu bisa diubah menjadi satu panggilan AWK alih-alih menyambungkan satu ke yang lain.
Dijeda sampai pemberitahuan lebih lanjut.

10

Saya akan menggunakan skrip python sederhana untuk pekerjaan ini karena telah dibangun di INI parser :

#!/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)

dan kemudian di 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]}

Tentu, ada implementasi yang lebih pendek di awk, tapi saya pikir ini lebih mudah dibaca dan lebih mudah untuk dipelihara.


3
Dalam versi bash sebelum 4.2, Anda perlu mendeklarasikan array rekanan sebelum mengisinya, misalnyaprint "declare -A %s" % (sec)
Felix Eve

2
Alih-alih eval:source <(cat in.ini | ./ini2arr.py)
Dijeda sampai pemberitahuan lebih lanjut.

3

Jika Anda ingin menghilangkan ruang ekstra, Anda dapat menggunakan fungsi bawaan gsub. Misalnya, Anda dapat menambahkan:

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

Ini akan menghapus semua ruang. Jika Anda ingin menghapus spasi di awal atau akhir token, Anda dapat menggunakannya

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

Trik keren. Tidak tahu ada fungsi bawaan :)
Flint

0

Inilah solusi bash murni.

Ini adalah versi baru dan lebih baik dari apa yang diposting chilladx:

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

Untuk contoh awal yang sangat mudah diikuti: Setelah Anda mengunduh ini, cukup salin file bash-ini-parser, dan scripts/file.inike direktori yang sama, lalu buat skrip pengujian klien menggunakan contoh yang saya berikan di bawah ini ke direktori yang sama juga.

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

Berikut ini beberapa peningkatan lebih lanjut yang saya buat pada skrip bash-ini-parser ...

Jika Anda ingin dapat membaca file ini dengan akhiran Windows serta Unix, tambahkan baris ini ke fungsi cfg_parser segera setelah yang membaca file:

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

Jika Anda ingin membaca file yang memiliki izin akses terbatas, tambahkan fungsi opsional ini:

# 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  
}

Harus downvote karena chmod 777. Meskipun praktik teduh paling baik, pasti tidak perlu membuat file ini dapat dieksekusi. Pendekatan yang lebih baik akan digunakan sudountuk membaca file, bukan untuk mengacaukan izin.
Richlv

@Richlv Oke. Saya sangat menghargai penjelasan suara turun. Tapi, itu adalah bagian kecil dari ini, yang signifikansi minimal sejauh menjawab pertanyaan secara keseluruhan. "Jawaban" adalah tautan: github.com/albfan/bash-ini-parser . Alih-alih memilih semuanya, untuk apa yang sudah diberi label fungsi pembungkus opsional, Anda bisa menyarankan edit.
BuvinJ

0

Selalu dengan asumsi memiliki Python ConfigParser sekitar, orang dapat membangun fungsi pembantu shell seperti ini:

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

$IFACEdan $parammasing-masing bagian parameter.

Pembantu ini kemudian memungkinkan panggilan seperti:

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

Semoga ini membantu!


0

Jika Anda memiliki Git tersedia dan OK dengan kendala tidak dapat menggunakan garis bawah pada nama-nama kunci, Anda dapat menggunakan git configsebagai parser / editor INI untuk keperluan umum.

Ini akan menangani parsing keluar pasangan kunci / nilai dari sekitar =dan membuang spasi kosong, ditambah Anda mendapatkan komentar (keduanya ;dan #) dan ketik paksaan pada dasarnya gratis. Saya telah menyertakan contoh kerja lengkap untuk input OP .inidan output yang diinginkan (array asosiatif Bash), di bawah ini.

Namun, diberikan file konfigurasi seperti ini

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

[section2]
    anothersetting = 42

... asalkan Anda hanya membutuhkan solusi cepat dan kotor, dan tidak menikah dengan gagasan memiliki pengaturan dalam array asosiatif Bash, Anda bisa lolos dengan sesedikit:

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

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

yang membuat variabel lingkungan bernama sectionname_variablenamedi lingkungan saat ini. Ini, tentu saja, hanya berfungsi jika Anda dapat percaya bahwa tidak ada nilai Anda yang akan mengandung suatu periode atau spasi (lihat di bawah untuk solusi yang lebih kuat).

Contoh sederhana lainnya

Mengambil nilai arbitrer, menggunakan fungsi shell untuk menyimpan pengetikan:

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

Sebuah alias akan baik-baik saja, di sini juga, tetapi itu biasanya tidak diperluas dalam skrip shell [ 1 ], dan lagi pula alias digantikan oleh fungsi shell "untuk hampir setiap tujuan," [ 2 ], menurut halaman manual Bash .

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

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

Dengan --typeopsi ini, Anda dapat "mengkanoniskan" pengaturan tertentu sebagai bilangan bulat, boolean, atau jalur (otomatis meluas ~):

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

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

Contoh cepat dan kotor yang sedikit lebih kuat

Jadikan semua variabel mytool.initersedia seperti SECTIONNAME_VARIABLENAMEdi lingkungan saat ini, pertahankan ruang putih internal dalam nilai-nilai utama:

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

Apa yang dilakukan ekspresi sed, dalam bahasa Inggris, adalah

  1. menemukan sekelompok karakter non-periode hingga periode, mengingatnya sebagai \1, lalu
  2. menemukan sekelompok karakter hingga tanda sama dengan, mengingat itu sebagai \2, dan
  3. menemukan semua karakter setelah tanda sama dengan \3
  4. akhirnya, di string pengganti
    • nama bagian + nama variabel adalah huruf besar, dan
    • bagian nilai dikutip ganda, jika mengandung karakter yang memiliki arti khusus untuk shell jika tidak dikutip (seperti spasi)

The \Udan \Eurutan dalam string pengganti (yang atas-hal bagian dari string pengganti) adalah GNU sedekstensi. Pada macOS dan BSD, Anda hanya akan menggunakan banyak -eekspresi untuk mencapai efek yang sama.

Berurusan dengan kutipan dan spasi kosong dalam nama bagian (yang git configmemungkinkan) dibiarkan sebagai latihan untuk pembaca.:)

Menggunakan nama bagian sebagai kunci ke dalam array asosiatif Bash

Diberikan:

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

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

Ini akan menghasilkan hasil yang diminta OP, hanya dengan mengatur ulang beberapa tangkapan dalam ekspresi pengganti sed, dan akan bekerja dengan baik tanpa GNU sed:

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

Saya memprediksi mungkin ada beberapa tantangan dengan mengutip untuk .inifile dunia nyata , tetapi berfungsi untuk contoh yang diberikan. Hasil:

declare -p {session,path}
# result:
# declare -A session=([barfoo]="bar" [foobar]="foo" )
# declare -A path=([barfoo]="/some/path" [foobar]="/some/path" )
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.