मैंने सिर्फ एक पार्सर लिखा था जिसे मैंने याय कहा था ! ( यम्ल यमलेसक नहीं है! ) जो यमलेस , यमशेल का एक छोटा उपसमुच्चय है। तो, अगर आप बैश के लिए 100% आज्ञाकारी YAML पार्सर की तलाश कर रहे हैं तो यह नहीं है। हालांकि, ओपी को उद्धृत करने के लिए, यदि आप एक संरचित कॉन्फ़िगरेशन फ़ाइल चाहते हैं जो कि गैर-तकनीकी उपयोगकर्ता के लिए जितना संभव हो उतना आसान है कि वह YAML की तरह हो, तो यह रुचि का हो सकता है।
यह पहले वाले उत्तर से हैरान है लेकिन मूल चर के बजाय साहचर्य सरणियों को लिखता है ( हाँ, इसके लिए बैश 4.x की आवश्यकता है )। यह एक तरह से ऐसा करता है जो डेटा को चाबियों के पूर्व ज्ञान के बिना पार्स करने की अनुमति देता है ताकि डेटा-संचालित कोड लिखा जा सके।
कुंजी / मान सरणी तत्वों के साथ-साथ, प्रत्येक सरणी में एक keys
सरणी होती है जिसमें प्रमुख नामों की सूची होती है, एक children
सरणी जिसमें बाल सरणियों के नाम और एक parent
कुंजी होती है जो अपने माता-पिता को संदर्भित करती है।
यह यमलेस्क का एक उदाहरण है:
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
) जो बैश संस्करण की एक विशेषता है। घोषणाएँ भी वैश्विक हैं ( -g
) इसलिए उन्हें एक फ़ंक्शन में निष्पादित किया जा सकता है लेकिन yay
सहायक जैसे वैश्विक दायरे में उपलब्ध हो सकता है :
yay() { eval $(yay_parse "$@"); }
इनपुट डेटा शुरू में साथ संसाधित किया जाता है sed
। यह ऐसी लाइनें छोड़ता है जो 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
वर्तमान पंक्ति के मांगपत्र के स्तर से ऊपर मांगपत्र स्तर पर महत्वपूर्ण है और संग्रह है कि मौजूदा लाइन का हिस्सा है प्रतिनिधित्व करता है। संग्रह की कुंजी / मान जोड़े अपने नाम का संयोजन के रूप में परिभाषित के साथ एक सरणी में संग्रहीत किया जाएगा prefix
और parent_key
।
शीर्ष स्तर (इंडेंट स्तर शून्य) के लिए डेटा सेट उपसर्ग का उपयोग मूल कुंजी के रूप में किया जाता है, इसलिए इसमें कोई उपसर्ग नहीं है (यह सेट है ""
)। अन्य सभी सरणियों को मूल उपसर्ग के साथ उपसर्ग किया जाता है।
इसके बाद, वर्तमान कुंजी को एक (awk-internal) सरणी में डाला जाता है जिसमें कुंजियाँ होती हैं। यह सरणी पूरे awk सत्र में बनी रहती है और इसलिए इसमें पूर्व लाइनों द्वारा डाली गई कुंजियाँ होती हैं। सरणी इंडेक्स के रूप में इंडेंट का उपयोग करके कुंजी को सरणी में डाला जाता है।
keys[indent] = key;
क्योंकि इस सरणी में पिछली पंक्तियों की कुंजियाँ शामिल हैं, वर्तमान पंक्ति के इंडेंट स्तर की तुलना में इंडेंट स्तर ग्रेटर वाली किसी भी कुंजी को हटा दिया जाता है:
for (i in keys) {if (i > indent) {delete keys[i]}}
यह की-चेन को की-चेन से रूट से इंडेंट लेवल पर करंट लाइन से करंट लाइन पर छोड़ता है। यह बासी कुंजियों को हटा देता है जो तब बनी रहती हैं जब पूर्व की रेखा वर्तमान लाइन की तुलना में अधिक गहरी होती थी।
अंतिम खंड bash
आदेशों को आउटपुट करता है : मूल्य के बिना एक इनपुट लाइन एक नया इंडेंट स्तर ( YAML parlance में एक संग्रह ) और मूल्य के साथ एक इनपुट लाइन वर्तमान संग्रह के लिए एक कुंजी जोड़ता है।
संग्रह का नाम वर्तमान पंक्ति के संयोजन है 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 eval
या source
बिल्ट-इन कमांड द्वारा bash कमांड के रूप में पार्स किया जा सकता है ।