จะแยกวิเคราะห์และแปลงไฟล์ ini เป็นตัวแปรอาเรย์ bash ได้อย่างไร?


13

ฉันพยายามแปลงไฟล์ ini เป็นตัวแปรอาเรย์ของ bash ตัวอย่าง ini ดังต่อไปนี้:

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

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

ดังนั้นสิ่งเหล่านี้จึงกลายเป็น:

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

และอื่น ๆ

ตอนนี้ฉันสามารถขึ้นมาด้วยคำสั่งนี้เท่านั้น

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

นอกจากนี้ยังมีปัญหาอื่นคือมันไม่ได้ใช้พื้นที่ใกล้=เข้าสู่การพิจารณา ฉันคิดว่าsedน่าจะเหมาะกว่าสำหรับงานนี้ แต่ฉันไม่รู้ว่าจะเก็บและเก็บตัวแปรชั่วคราวสำหรับชื่อส่วนsedได้อย่างไร

ดังนั้นความคิดวิธีการทำเช่นนี้?


ถ้ามีวิธีที่มีประสิทธิภาพอื่น ๆ ที่จะทำเช่นนี้รู้สึกอิสระที่จะโพสต์วิธีการแก้ปัญหาของคุณมากเกินไป :)
หินเหล็กไฟ


สำหรับวิธีแก้ปัญหาง่ายๆให้ตรวจสอบ: ฉันจะรับค่า INI ภายในเชลล์สคริปต์ได้อย่างไร ที่ stackoverflow SE
kenorb

คำตอบ:


10

Gawk ยอมรับการแสดงออกปกติเป็นตัวคั่นฟิลด์ ต่อไปนี้กำจัดช่องว่างรอบเครื่องหมายเท่ากับ แต่เก็บไว้ในส่วนที่เหลือของบรรทัด เครื่องหมายคำพูดจะถูกเพิ่มเข้าไปรอบ ๆ ค่าเพื่อให้มีการเว้นวรรคเหล่านั้นถ้ามีการเก็บรักษาไว้เมื่อทำการมอบหมาย Bash ฉันสมมติว่าชื่อส่วนจะเป็นตัวแปรตัวเลข แต่ถ้าคุณใช้ Bash 4 มันจะง่ายต่อการปรับเปลี่ยนนี้เพื่อใช้อาร์เรย์ที่เชื่อมโยงกับชื่อส่วนเองว่าเป็นดัชนี

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

โปรดทราบว่าคุณอาจต้องการลบพื้นที่ที่ Khaled แสดง (เฉพาะ $ 1 และส่วน) เนื่องจากชื่อตัวแปร Bash ไม่สามารถมีช่องว่างได้

นอกจากนี้วิธีนี้จะไม่ทำงานหากค่ามีเครื่องหมายเท่ากับ

อีกเทคนิคหนึ่งคือการใช้ Bash while readloop และทำการกำหนดเมื่อไฟล์นั้นถูกอ่านdeclareซึ่งจะปลอดภัยจากเนื้อหาที่เป็นอันตรายส่วนใหญ่

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

อีกครั้งอาร์เรย์ที่เชื่อมโยงได้รับการสนับสนุนอย่างง่ายดายพอสมควร


1
ข้อมูลที่ดีมากและฉันชอบเทคนิคที่สองเป็นพิเศษเพราะมันใช้ฟังก์ชัน bash ในตัวแทนที่จะใช้คำสั่งภายนอก
Flint

@TonyBarganski: นั่นสามารถแก้ไขได้ในการโทร AWK ครั้งเดียวแทนที่จะทำการโทรเข้าแบบอื่น
หยุดชั่วคราวจนกว่าจะมีการแจ้งให้ทราบต่อไป

10

ฉันจะใช้สคริปต์ python แบบง่ายสำหรับงานนี้เพราะมันสร้างขึ้นใน 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)

แล้วในทุบตี:

#!/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]}

แน่นอนว่ามีการใช้งานที่สั้นลงใน awk แต่ฉันคิดว่านี่อ่านได้ง่ายและง่ายต่อการดูแลรักษา


3
ในเวอร์ชั่นทุบตีก่อนหน้า 4.2 มีความจำเป็นต้องประกาศอาร์เรย์ที่เกี่ยวข้องก่อนที่จะเติมมันเช่นprint "declare -A %s" % (sec)
เฟลิกซ์อีฟ

2
แทนที่จะเป็นeval:source <(cat in.ini | ./ini2arr.py)
หยุดชั่วคราวจนกว่าจะมีประกาศเพิ่มเติม

3

หากคุณต้องการกำจัดช่องว่างเพิ่มเติมคุณสามารถใช้ฟังก์ชันในgsubตัว ตัวอย่างเช่นคุณสามารถเพิ่ม:

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

จะเป็นการลบช่องว่างทั้งหมด หากคุณต้องการลบช่องว่างที่จุดเริ่มต้นหรือจุดสิ้นสุดโทเค็นคุณสามารถใช้

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

เทคนิคเด็ด ๆ ไม่ทราบว่ามันมีฟังก์ชั่นในตัว :)
ฟลินท์

0

นี่เป็นวิธีทุบตีบริสุทธิ์

นี่เป็นเวอร์ชั่นใหม่และปรับปรุงของสิ่งที่ chilladx โพสต์:

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

เพื่อง่ายต่อการปฏิบัติตามตัวอย่างเริ่มต้น: หลังจากคุณดาวน์โหลดไฟล์นี้เพียงแค่คัดลอกไฟล์bash-ini-parserและscripts/file.iniไปยังไดเรกทอรีเดียวกันจากนั้นสร้างสคริปต์ทดสอบไคลเอ็นต์โดยใช้ตัวอย่างที่ฉันให้ไว้ด้านล่างไปยังไดเรกทอรีเดียวกัน

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

นี่คือการปรับปรุงเพิ่มเติมบางอย่างที่ฉันทำกับสคริปต์ bash-ini-parser ...

หากคุณต้องการให้สามารถอ่านไฟล์ ini ด้วยการสิ้นสุดบรรทัด Windows และ Unix ให้เพิ่มบรรทัดนี้ในฟังก์ชัน cfg_parser ทันทีต่อจากไฟล์ที่อ่านไฟล์:

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

หากคุณต้องการอ่านไฟล์ที่มีสิทธิ์การเข้าถึงที่ จำกัด ให้เพิ่มฟังก์ชั่นเสริมนี้:

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

ต้อง downvote chmod 777เพราะ ในขณะที่การฝึกฝนที่ร่มรื่นที่สุดก็ไม่จำเป็นต้องทำให้ไฟล์ ini สามารถใช้งานได้ วิธีที่ดีกว่าคือการใช้sudoเพื่ออ่านไฟล์ไม่ให้ยุ่งกับการอนุญาต
Richlv

@Richlv ตกลง ฉันขอขอบคุณคำอธิบายการลงคะแนน แต่นั่นเป็นส่วนเล็ก ๆ ของสิ่งนี้ซึ่งมีความสำคัญน้อยที่สุดเท่าที่ตอบคำถามโดยรวม ว่า "คำตอบ" คือลิงค์: github.com/albfan/bash-ini-parser แทนที่จะลงคะแนนทั้งหมดสิ่งสำหรับสิ่งที่มีอยู่แล้วฉลากฟังก์ชั่นเสริมที่ห่อหุ้มคุณอาจได้แนะนำการแก้ไข
BuvinJ

0

สมมติว่ามี ConfigParser ของ Python อยู่เสมอหนึ่งอาจสร้างฟังก์ชันตัวช่วยเชลล์เช่นนี้:

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

$IFACEและ$paramเป็นส่วนตามลำดับพารามิเตอร์

ผู้ช่วยนี้อนุญาตการโทรเช่น:

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

หวังว่านี่จะช่วยได้!


0

หากคุณมี Git และตกลงกับข้อ จำกัด ที่ไม่สามารถใช้เครื่องหมายขีดล่างในชื่อคีย์คุณสามารถใช้git configเป็นตัวแยกวิเคราะห์ / ตัวแก้ไข INI เอนกประสงค์

มันจะจัดการแยกคีย์คู่ / ค่าจากรอบ ๆ=และยกเลิกช่องว่างที่ไม่มีนัยสำคัญรวมทั้งคุณได้รับความคิดเห็น (ทั้ง;และ#) และประเภทการข่มขู่โดยทั่วไปฟรี ฉันได้รวมตัวอย่างการทำงานที่สมบูรณ์สำหรับอินพุตของ OP .iniและเอาท์พุทที่ต้องการ (อาเรย์เชื่อมโยง Bash) ไว้ด้านล่าง

อย่างไรก็ตามให้ไฟล์ config แบบนี้

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

[section2]
    anothersetting = 42

... หากคุณต้องการวิธีแก้ปัญหาที่รวดเร็วและสกปรกและไม่ได้แต่งงานกับความคิดของการตั้งค่าในอาเรย์ของ Bash associative คุณสามารถทำได้โดยใช้:

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

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

ซึ่งสร้างตัวแปรสภาพแวดล้อมที่มีชื่อsectionname_variablenameในสภาพแวดล้อมปัจจุบัน แน่นอนว่าสิ่งนี้ใช้ได้ผลก็ต่อเมื่อคุณสามารถเชื่อมั่นได้ว่าไม่มีค่าใดที่จะมีจุดหรือช่องว่าง (ดูด้านล่างสำหรับวิธีแก้ปัญหาที่มีประสิทธิภาพมากกว่า)

ตัวอย่างง่ายๆอื่น ๆ

ดึงค่าตามอำเภอใจโดยใช้ฟังก์ชั่นเปลือกเพื่อบันทึกการพิมพ์:

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

นามแฝงจะตกลงที่นี่ด้วย แต่ผู้ที่ยังไม่ได้ขยายตัวได้ตามปกติในสคริปต์เชลล์ [ 1 ] และชื่อแทนอยู่แล้วจะถูกแทนที่โดยฟังก์ชั่นเปลือก "เกือบทุกจุดประสงค์" [ 2 ] ตามที่ทุบตีหน้าคน

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

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

ด้วย--typeตัวเลือกนี้คุณสามารถตั้งค่า "canonicalize" เป็นจำนวนเต็มบูลีนหรือพา ธ (ขยายโดยอัตโนมัติ~):

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

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

ตัวอย่างรวดเร็วและสกปรกที่แข็งแกร่งขึ้นเล็กน้อย

ทำให้ตัวแปรทั้งหมดmytool.iniพร้อมใช้งานเช่นเดียวกับSECTIONNAME_VARIABLENAMEในสภาพแวดล้อมปัจจุบันรักษาช่องว่างภายในในค่าคีย์:

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

สิ่งที่แสดงออกอย่างใจเย็นกำลังทำในภาษาอังกฤษคือ

  1. การค้นหากลุ่มของอักขระที่ไม่ใช่ช่วงเวลาจนถึงจุดหนึ่งโดยจดจำว่าเป็น\1แล้ว
  2. การค้นหากลุ่มของอักขระจนถึงเครื่องหมายเท่ากับให้จำว่าเป็น\2และ
  3. การค้นหาตัวอักษรทั้งหมดหลังจากเครื่องหมายเท่ากับ \3
  4. ในที่สุดในสตริงแทนที่
    • ชื่อส่วน + ชื่อตัวแปรเป็นตัวพิมพ์ใหญ่และ
    • ส่วนของค่าคือการเสนอราคาสองครั้งในกรณีที่มันมีตัวละครที่มีความหมายพิเศษกับเปลือกถ้าไม่ได้ยกมา (เช่นช่องว่าง)

\Uและ\Eวนเวียนอยู่ในสตริงทดแทน (ซึ่งบนกรณีที่เป็นส่วนหนึ่งของสตริงทดแทน) เป็น GNU sedนามสกุล สำหรับ macOS และ BSD คุณเพียงแค่ใช้หลาย-eนิพจน์เพื่อให้ได้ผลลัพธ์ที่เหมือนกัน

การจัดการกับอัญประกาศและช่องว่างที่ฝังอยู่ในชื่อส่วน (ซึ่งgit configอนุญาตให้) ถูกทิ้งไว้เป็นแบบฝึกหัดสำหรับผู้อ่าน:)

การใช้ชื่อส่วนเป็นคีย์ในอาเรย์ของ Bash

ได้รับ:

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

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

สิ่งนี้จะสร้างผลลัพธ์ที่ OP ต้องการโดยเพียงแค่จัดเรียงบางส่วนใหม่ในนิพจน์แทนที่ sed และจะทำงานได้ดีโดยไม่ต้อง GNU sed:

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

ฉันคาดการณ์ว่าอาจมีความท้าทายบางอย่างจากการอ้างถึง.iniไฟล์ในโลกแห่งความจริงแต่มันก็ใช้ได้กับตัวอย่างที่มีให้ ผลลัพธ์:

declare -p {session,path}
# result:
# declare -A session=([barfoo]="bar" [foobar]="foo" )
# declare -A path=([barfoo]="/some/path" [foobar]="/some/path" )
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.