ฉันเพิ่งเขียนโปรแกรมแยกวิเคราะห์ที่ฉันเรียกว่าย่า! ( Yaml ไม่ใช่ Yamlesque! ) ซึ่งแยกYamlesqueซึ่งเป็นเซตย่อยของ YAML ดังนั้นหากคุณกำลังมองหาตัวแยกวิเคราะห์ YAML ที่สอดคล้องกับ Bash ได้ 100% นั่นไม่ใช่สิ่งนี้ อย่างไรก็ตามในการอ้างอิง OP หากคุณต้องการไฟล์การกำหนดค่าแบบโครงสร้างซึ่งเป็นเรื่องง่ายที่สุดสำหรับผู้ใช้ที่ไม่ใช่ด้านเทคนิคในการแก้ไขที่คล้ายกับ YAML นี่อาจเป็นที่สนใจ
มันถูกinspred โดยคำตอบก่อนหน้าแต่เขียนอาเรย์เชื่อมโยง ( ใช่มันต้อง Bash 4.x ) แทนตัวแปรพื้นฐาน มันทำในลักษณะที่ช่วยให้สามารถแยกวิเคราะห์ข้อมูลโดยไม่ทราบล่วงหน้าเกี่ยวกับคีย์เพื่อให้สามารถเขียนโค้ดที่ขับเคลื่อนด้วยข้อมูลได้
เช่นเดียวกับองค์ประกอบอาร์เรย์คีย์ / ค่าแต่ละอาร์เรย์มีkeys
อาร์เรย์ที่มีรายการชื่อคีย์children
อาร์เรย์ที่มีชื่ออาร์เรย์ย่อยและparent
คีย์ที่อ้างอิงถึงแม่
นี่คือตัวอย่างของ Yamlesque:
root_key1: this is value one
root_key2: "this is value two"
drink:
state: liquid
coffee:
best_served: hot
colour: brown
orange_juice:
best_served: cold
colour: orange
food:
state: solid
apple_pie:
best_served: warm
root_key_3: this is value three
นี่คือตัวอย่างที่แสดงวิธีใช้:
#!/bin/bash
# An example showing how to use Yay
. /usr/lib/yay
# helper to get array value at key
value() { eval echo \${$1[$2]}; }
# print a data collection
print_collection() {
for k in $(value $1 keys)
do
echo "$2$k = $(value $1 $k)"
done
for c in $(value $1 children)
do
echo -e "$2$c\n$2{"
print_collection $c " $2"
echo "$2}"
done
}
yay example
print_collection example
ผลลัพธ์ใด:
root_key1 = this is value one
root_key2 = this is value two
root_key_3 = this is value three
example_drink
{
state = liquid
example_coffee
{
best_served = hot
colour = brown
}
example_orange_juice
{
best_served = cold
colour = orange
}
}
example_food
{
state = solid
example_apple_pie
{
best_served = warm
}
}
และนี่คือตัวแยกวิเคราะห์:
yay_parse() {
# find input file
for f in "$1" "$1.yay" "$1.yml"
do
[[ -f "$f" ]] && input="$f" && break
done
[[ -z "$input" ]] && exit 1
# use given dataset prefix or imply from file name
[[ -n "$2" ]] && local prefix="$2" || {
local prefix=$(basename "$input"); prefix=${prefix%.*}
}
echo "declare -g -A $prefix;"
local s='[[:space:]]*' w='[a-zA-Z0-9_]*' fs=$(echo @|tr @ '\034')
sed -n -e "s|^\($s\)\($w\)$s:$s\"\(.*\)\"$s\$|\1$fs\2$fs\3|p" \
-e "s|^\($s\)\($w\)$s:$s\(.*\)$s\$|\1$fs\2$fs\3|p" "$input" |
awk -F$fs '{
indent = length($1)/2;
key = $2;
value = $3;
# No prefix or parent for the top level (indent zero)
root_prefix = "'$prefix'_";
if (indent ==0 ) {
prefix = ""; parent_key = "'$prefix'";
} else {
prefix = root_prefix; parent_key = keys[indent-1];
}
keys[indent] = key;
# remove keys left behind if prior row was indented more than this row
for (i in keys) {if (i > indent) {delete keys[i]}}
if (length(value) > 0) {
# value
printf("%s%s[%s]=\"%s\";\n", prefix, parent_key , key, value);
printf("%s%s[keys]+=\" %s\";\n", prefix, parent_key , key);
} else {
# collection
printf("%s%s[children]+=\" %s%s\";\n", prefix, parent_key , root_prefix, key);
printf("declare -g -A %s%s;\n", root_prefix, key);
printf("%s%s[parent]=\"%s%s\";\n", root_prefix, key, prefix, parent_key);
}
}'
}
# helper to load yay data file
yay() { eval $(yay_parse "$@"); }
มีเอกสารบางอย่างในไฟล์ต้นฉบับที่เชื่อมโยงและด้านล่างนี้เป็นคำอธิบายสั้น ๆ ว่าโค้ดทำอะไร
yay_parse
ฟังก์ชั่นแรกที่ตั้งอยู่input
ไฟล์หรือทางออกที่มีสถานะออกจาก 1. ถัดไปก็จะเป็นตัวกำหนดชุดข้อมูลprefix
ทั้งระบุไว้อย่างชัดเจนหรือได้มาจากชื่อไฟล์
โดยจะเขียนbash
คำสั่งที่ถูกต้องไปยังเอาต์พุตมาตรฐานซึ่งหากดำเนินการจะกำหนดอาร์เรย์ที่แสดงเนื้อหาของไฟล์ข้อมูลอินพุต ครั้งแรกของเหล่านี้กำหนดอาร์เรย์ระดับบนสุด:
echo "declare -g -A $prefix;"
โปรดทราบว่าการประกาศอาร์เรย์เป็นแบบเชื่อมโยง ( -A
) ซึ่งเป็นคุณลักษณะของ Bash เวอร์ชัน 4 การประกาศเป็นแบบโกลบอล ( -g
) ดังนั้นจึงสามารถเรียกใช้งานได้ในฟังก์ชัน แต่สามารถใช้งานได้ในขอบเขตส่วนกลางเช่นตัวyay
ช่วย:
yay() { eval $(yay_parse "$@"); }
sed
ป้อนข้อมูลจะถูกประมวลผลครั้งแรกกับ โดยจะลดบรรทัดที่ไม่ตรงกับข้อกำหนดคุณสมบัติการจัดรูปแบบ Yamlesque ก่อนที่จะลบเขตข้อมูล Yamlesque ที่ถูกต้องด้วยอักขระตัวคั่นไฟล์ ASCII และลบเครื่องหมายคำพูดคู่ใด ๆ ที่ล้อมรอบเขตข้อมูลค่า
local s='[[:space:]]*' w='[a-zA-Z0-9_]*' fs=$(echo @|tr @ '\034')
sed -n -e "s|^\($s\)\($w\)$s:$s\"\(.*\)\"$s\$|\1$fs\2$fs\3|p" \
-e "s|^\($s\)\($w\)$s:$s\(.*\)$s\$|\1$fs\2$fs\3|p" "$input" |
นิพจน์ทั้งสองมีความคล้ายคลึงกัน พวกเขาแตกต่างกันเพียงเพราะคนแรกเลือกค่าที่ยกมาซึ่งเป็นคนที่สองเลือกค่าที่ไม่ยกมา
ตัวคั่นไฟล์ (28 / ฐานสิบหก 12 / ฐานแปด 034) ถูกใช้เนื่องจากเป็นอักขระที่ไม่สามารถพิมพ์ได้จึงไม่น่าจะอยู่ในข้อมูลอินพุต
ผลลัพธ์จะถูกไพพ์ไปawk
ที่กระบวนการอินพุตหนึ่งบรรทัดในแต่ละครั้ง มันใช้อักขระFSเพื่อกำหนดแต่ละฟิลด์ให้กับตัวแปร:
indent = length($1)/2;
key = $2;
value = $3;
ทุกบรรทัดมีการเยื้อง (อาจเป็นศูนย์) และคีย์ แต่พวกเขาไม่มีค่า มันคำนวณระดับเยื้องสำหรับเส้นแบ่งความยาวของสนามแรกซึ่งมีช่องว่างนำโดยสอง รายการระดับบนสุดที่ไม่มีการเยื้องใด ๆ จะอยู่ที่ระดับศูนย์เยื้อง
ถัดไปมันจะทำงานสิ่งที่prefix
ใช้สำหรับรายการปัจจุบัน นี่คือสิ่งที่ได้รับการเพิ่มชื่อคีย์เพื่อสร้างชื่ออาร์เรย์ มีroot_prefix
สำหรับอาร์เรย์ระดับบนสุดซึ่งกำหนดเป็นชื่อชุดข้อมูลและขีดล่าง:
root_prefix = "'$prefix'_";
if (indent ==0 ) {
prefix = ""; parent_key = "'$prefix'";
} else {
prefix = root_prefix; parent_key = keys[indent-1];
}
parent_key
เป็นกุญแจสำคัญที่ระดับเยื้องเหนือระดับเยื้องบรรทัดปัจจุบันและแสดงให้เห็นถึงคอลเลกชันที่บรรทัดปัจจุบันเป็นส่วนหนึ่งของ คอลเลกชันของคู่คีย์ / ค่าจะถูกเก็บไว้ในอาร์เรย์ที่มีชื่อของตนกำหนดเป็น concatenation ของที่และprefix
parent_key
สำหรับระดับสูงสุด (ศูนย์เยื้องระดับ) คำนำหน้าชุดข้อมูลจะใช้เป็นคีย์หลักจึงไม่มีคำนำหน้า (ตั้งค่าเป็น""
) อาร์เรย์อื่นทั้งหมดนำหน้าด้วยรูทนำหน้า
ถัดไปคีย์ปัจจุบันจะถูกแทรกลงในอาร์เรย์ (awk-internal) ที่มีคีย์ อาร์เรย์นี้ยังคงอยู่ตลอดเซสชัน awk ทั้งหมดและดังนั้นจึงมีปุ่มที่แทรกโดยบรรทัดก่อนหน้า คีย์ถูกแทรกเข้าไปในอาร์เรย์โดยใช้การเยื้องเป็นดัชนีดัชนี
keys[indent] = key;
เนื่องจากอาร์เรย์นี้มีคีย์จากบรรทัดก่อนหน้าคีย์ใด ๆ ที่มีตัวขูดระดับเยื้องกว่าระดับเยื้องของบรรทัดปัจจุบันจะถูกลบออก:
for (i in keys) {if (i > indent) {delete keys[i]}}
สิ่งนี้ทำให้อาร์เรย์ของคีย์ประกอบด้วยคีย์เชนจากรูทที่เยื้องระดับ 0 ถึงบรรทัดปัจจุบัน มันจะลบกุญแจค้างที่ยังคงอยู่เมื่อบรรทัดก่อนหน้าถูกเยื้องลึกกว่าบรรทัดปัจจุบัน
ส่วนสุดท้ายส่งออกbash
คำสั่ง: บรรทัดอินพุตที่ไม่มีค่าจะเริ่มระดับเยื้องใหม่ ( คอลเลกชันในการพูดจาหยาบคาย) และบรรทัดอินพุตที่มีค่าจะเพิ่มคีย์ไปยังคอลเลกชันปัจจุบัน
ชื่อคอลเลกชันนี้เป็นกำหนดการบรรทัดปัจจุบันของและprefix
parent_key
เมื่อคีย์มีค่าคีย์ที่มีค่านั้นจะถูกกำหนดให้กับคอลเลกชันปัจจุบันดังนี้:
printf("%s%s[%s]=\"%s\";\n", prefix, parent_key , key, value);
printf("%s%s[keys]+=\" %s\";\n", prefix, parent_key , key);
คำสั่งแรกส่งคำสั่งเพื่อกำหนดค่าให้กับองค์ประกอบอาเรย์แบบเชื่อมโยงที่ตั้งชื่อตามคีย์และคำสั่งที่สองส่งคำสั่งเพื่อเพิ่มคีย์ลงในkeys
รายการที่คั่นด้วยช่องว่างของคอลเลกชัน:
<current_collection>[<key>]="<value>";
<current_collection>[keys]+=" <key>";
เมื่อคีย์ไม่มีค่าคอลเล็กชันใหม่จะเริ่มต้นดังนี้:
printf("%s%s[children]+=\" %s%s\";\n", prefix, parent_key , root_prefix, key);
printf("declare -g -A %s%s;\n", root_prefix, key);
คำสั่งแรกส่งออกคำสั่งเพื่อเพิ่มคอลเลกชันใหม่ไปยังchildren
รายการที่คั่นด้วยช่องว่างของคอลเลกชันปัจจุบันและคำสั่งที่สองส่งคำสั่งเพื่อประกาศอาเรย์เชื่อมโยงใหม่สำหรับคอลเลกชันใหม่:
<current_collection>[children]+=" <new_collection>"
declare -g -A <new_collection>;
เอาต์พุตทั้งหมดจากyay_parse
สามารถแยกวิเคราะห์เป็นคำสั่ง bash โดยคำสั่ง bash eval
หรือsource
built-in