Làm thế nào để phân tích và chuyển đổi tập tin ini thành các biến bash mảng?


12

Tôi đang cố gắng chuyển đổi một tập tin ini thành các biến mảng bash. Mẫu ini như sau:

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

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

vì vậy những điều này trở thành:

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

và như thế.

Ngay bây giờ, tôi chỉ có thể đưa ra lệnh này

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

Ngoài ra, một vấn đề khác là, nó không mất nhiều thời gian =để xem xét. Tôi nghĩ sedcó lẽ phù hợp hơn với công việc này nhưng tôi không biết cách giữ và lưu trữ một biến tạm thời cho tên phần sed.

Vì vậy, bất kỳ ý tưởng làm thế nào để làm điều này?


Nếu có cách hiệu quả khác để làm điều này, vui lòng đăng giải pháp của bạn quá :)
Flint


Đối với giải pháp đơn giản, hãy kiểm tra: Làm cách nào để lấy giá trị INI trong tập lệnh shell? tại stackoverflow SE.
kenorb

Câu trả lời:


10

Gawk chấp nhận các biểu thức chính quy như các dấu phân cách trường. Sau đây loại bỏ khoảng trắng xung quanh dấu bằng, nhưng bảo tồn chúng trong phần còn lại của dòng. Các trích dẫn được thêm vào xung quanh giá trị để các khoảng trắng đó, nếu có, được giữ nguyên khi gán Bash được thực hiện. Tôi giả sử rằng tên của phần sẽ là biến số, nhưng nếu bạn đang sử dụng Bash 4, có thể dễ dàng điều chỉnh điều này để sử dụng mảng kết hợp với chính tên của phần đó làm chỉ mục.

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

Lưu ý rằng bạn cũng có thể muốn thực hiện loại bỏ không gian mà Khaled hiển thị (chỉ trên $ 1 và phần) vì tên biến Bash không thể chứa dấu cách.

Ngoài ra, phương pháp này sẽ không hoạt động nếu các giá trị chứa các dấu bằng nhau.

Một kỹ thuật khác là sử dụng while readvòng lặp Bash và thực hiện các bài tập khi tệp được đọc, sử dụng declarean toàn với hầu hết các nội dung độc hại.

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

Một lần nữa, mảng kết hợp có thể dễ dàng được hỗ trợ.


1
Thông tin rất hay và tôi đặc biệt thích kỹ thuật thứ hai vì nó sử dụng hàm bash tích hợp sẵn, thay vì dựa vào lệnh bên ngoài.
Flint

@TonyBarganski: Điều đó có thể được sửa đổi thành một cuộc gọi AWK thay vì chuyển cuộc gọi này sang cuộc gọi khác.
Tạm dừng cho đến khi có thông báo mới.

10

Tôi sẽ sử dụng tập lệnh python đơn giản cho công việc này vì nó đã được xây dựng trong trình phân tích cú pháp INI :

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

và sau đó trong 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]}

Chắc chắn, có những triển khai ngắn hơn trong awk, nhưng tôi nghĩ rằng điều này dễ đọc hơn và dễ bảo trì hơn.


3
Trong các phiên bản bash trước 4.2, cần phải khai báo một mảng liên kết trước khi điền vào nó, ví dụ:print "declare -A %s" % (sec)
Đêm giao thừa

2
Thay vì eval:source <(cat in.ini | ./ini2arr.py)
Tạm dừng cho đến khi có thông báo mới.

3

Nếu bạn muốn loại bỏ các khoảng trắng thừa, bạn có thể sử dụng chức năng tích hợp gsub. Ví dụ: bạn có thể thêm:

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

Điều này sẽ loại bỏ tất cả các không gian. Nếu bạn muốn xóa khoảng trắng ở đầu hoặc cuối mã thông báo, bạn có thể sử dụng

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

Thủ đoạn tuyệt vời. Không biết có chức năng tích hợp như vậy :)
Flint

0

Đây là một giải pháp bash tinh khiết.

Đây là phiên bản mới và được cải tiến của những gì chilladx đã đăng:

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

Để có một ví dụ thực sự dễ thực hiện: Sau khi bạn tải xuống tệp này, chỉ cần sao chép các tệp bash-ini-parserscripts/file.inivào cùng thư mục, sau đó tạo tập lệnh kiểm tra máy khách bằng cách sử dụng ví dụ tôi đã cung cấp bên dưới vào cùng thư mục đó.

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

Dưới đây là một số cải tiến khác mà tôi đã thực hiện đối với tập lệnh bash-ini-Parser ...

Nếu bạn muốn có thể đọc các tệp ini với các kết thúc dòng Windows cũng như Unix, hãy thêm dòng này vào hàm cfg_parser ngay sau hàm đọc tệp:

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

Nếu bạn muốn đọc các tệp có quyền truy cập hạn chế, hãy thêm chức năng tùy chọn này:

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

Phải downvote vì chmod 777. Mặc dù thực hành mờ ám ở mức tốt nhất, chắc chắn không cần phải thực hiện tệp ini. Một cách tiếp cận tốt hơn sẽ là sử dụng sudođể đọc tệp, không gây rối với các quyền.
Richlv

@Richlv Ok. Tôi đánh giá cao sự giải thích bỏ phiếu xuống. Nhưng, đó là một phần rất nhỏ trong số này, có ý nghĩa tối thiểu cho đến khi trả lời toàn bộ câu hỏi. "Câu trả lời" là liên kết: github.com/albfan/bash-ini-parser . Thay vì bỏ phiếu toàn bộ, đối với những gì đã gắn nhãn chức năng trình bao bọc tùy chọn, bạn có thể đã đề xuất một chỉnh sửa.
BuvinJ

0

Luôn luôn giả sử có Trình cấu hình của Python xung quanh, người ta có thể xây dựng một hàm trợ giúp trình bao như thế này:

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

$IFACE$paramlà phần tương ứng của tham số.

Người trợ giúp này sau đó cho phép các cuộc gọi như:

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

Hi vọng điêu nay co ich!


0

Nếu bạn có sẵn Git và ổn với điều kiện không thể sử dụng dấu gạch dưới trong tên khóa, bạn có thể sử dụng git configlàm trình phân tích / trình soạn thảo INI cho mục đích chung.

Nó sẽ xử lý phân tích cặp khóa / giá trị từ xung quanh = và loại bỏ khoảng trắng không đáng kể, cộng với việc bạn nhận được bình luận (cả ;#) và loại cưỡng chế về cơ bản miễn phí. Tôi đã bao gồm một ví dụ hoạt động hoàn chỉnh cho đầu vào của OP .inivà đầu ra mong muốn (mảng kết hợp Bash), bên dưới.

Tuy nhiên, được cung cấp một tệp cấu hình như thế này

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

[section2]
    anothersetting = 42

Càng cung cấp cho bạn chỉ cần một giải pháp nhanh chóng và bẩn thỉu, và không kết hôn với ý tưởng có các thiết lập trong một mảng kết hợp Bash, bạn có thể thoát khỏi chỉ với:

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

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

mà tạo ra các biến môi trường được đặt tên sectionname_variablenametrong môi trường hiện tại. Tất nhiên, điều này chỉ hoạt động nếu bạn có thể tin tưởng rằng không có giá trị nào của bạn sẽ chứa một khoảng thời gian hoặc khoảng trắng (xem bên dưới để biết giải pháp mạnh mẽ hơn).

Ví dụ đơn giản khác

Tìm nạp các giá trị tùy ý, sử dụng hàm shell để lưu kiểu gõ:

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

Một bí danh cũng sẽ ổn, ở đây, nhưng những cái đó thường không được mở rộng trong tập lệnh shell [ 1 ], và dù sao thì các bí danh được thay thế bởi các hàm shell "cho hầu hết mọi mục đích", [ 2 ], theo trang Bash man .

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

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

Với --typetùy chọn, bạn có thể "chuẩn hóa" các cài đặt cụ thể dưới dạng số nguyên, booleans hoặc đường dẫn (tự động mở rộng ~):

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

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

Ví dụ nhanh và bẩn mạnh mẽ hơn một chút

Làm cho tất cả các biến mytool.inicó sẵn như SECTIONNAME_VARIABLENAMEtrong môi trường hiện tại, duy trì khoảng trắng bên trong trong các giá trị chính:

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

Những gì biểu hiện sed đang làm, trong tiếng Anh, là

  1. tìm thấy một loạt các nhân vật không có thời hạn cho đến một khoảng thời gian \1, sau đó nhớ rằng
  2. tìm thấy một loạt các ký tự cho đến một dấu bằng, nhớ rằng như \2, và
  3. tìm tất cả các ký tự sau dấu bằng \3
  4. cuối cùng, trong chuỗi thay thế
    • tên phần + tên biến được viết hoa và
    • phần giá trị được trích dẫn kép, trong trường hợp nó chứa các ký tự có ý nghĩa đặc biệt với shell nếu không được trích dẫn (như khoảng trắng)

Các chuỗi \U\Echuỗi trong chuỗi thay thế (chữ hoa là một phần của chuỗi thay thế) là sedphần mở rộng GNU . Trên macOS và BSD, bạn chỉ cần sử dụng nhiều-e biểu thức để đạt được hiệu ứng tương tự.

Xử lý các trích dẫn và khoảng trắng được nhúng trong tên phần ( git configcho phép) được để lại như một bài tập cho người đọc.:)

Sử dụng tên phần làm khóa vào một mảng kết hợp Bash

Được:

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

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

Điều này sẽ tạo ra kết quả mà OP đang yêu cầu, chỉ đơn giản bằng cách sắp xếp lại một số ảnh chụp trong biểu thức thay thế sed và sẽ hoạt động tốt mà không cần GNU sed:

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

Tôi dự đoán có thể có một số thách thức với việc trích dẫn cho một .initệp trong thế giới thực , nhưng nó hoạt động với ví dụ được cung cấp. Kết quả:

declare -p {session,path}
# result:
# declare -A session=([barfoo]="bar" [foobar]="foo" )
# declare -A path=([barfoo]="/some/path" [foobar]="/some/path" )
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.