Saya baru saja menulis parser yang saya sebut Yay! ( Yaml bukan Yamlesque! ) Yang mem-parsing Yamlesque , sebagian kecil dari YAML. Jadi, jika Anda mencari parser YAML yang 100% sesuai untuk Bash maka ini bukan. Namun, untuk mengutip OP, jika Anda ingin file konfigurasi terstruktur yang semudah mungkin untuk pengguna non-teknis untuk mengedit yang seperti YAML, ini mungkin menarik.
Itu diinspirasi oleh jawaban sebelumnya tetapi menulis array asosiatif ( ya, ini membutuhkan Bash 4.x ) bukan variabel dasar. Ia melakukannya dengan cara yang memungkinkan data diurai tanpa pengetahuan sebelumnya tentang kunci sehingga kode yang digerakkan oleh data dapat ditulis.
Selain elemen array kunci / nilai, setiap array memiliki keys
array yang berisi daftar nama kunci, children
array yang berisi nama array anak dan parent
kunci yang merujuk ke induknya.
Ini adalah contoh dari 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
Berikut adalah contoh yang menunjukkan cara menggunakannya:
#!/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
yang keluaran:
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
}
}
Dan di sini adalah pengurai:
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 "$@"); }
Ada beberapa dokumentasi dalam file sumber tertaut dan di bawah ini adalah penjelasan singkat tentang apa yang kode lakukan.
The yay_parse
Fungsi pertama menempatkan input
file atau keluar dengan status keluar dari 1. Berikutnya, menentukan dataset prefix
, baik secara eksplisit ditentukan atau berasal dari nama file.
Ini menulis bash
perintah yang valid untuk output standar yang, jika dijalankan, menentukan array yang mewakili konten file data input. Yang pertama dari ini mendefinisikan array tingkat atas:
echo "declare -g -A $prefix;"
Perhatikan bahwa deklarasi array adalah asosiatif ( -A
) yang merupakan fitur dari Bash versi 4. Deklarasi juga global ( -g
) sehingga dapat dieksekusi dalam suatu fungsi tetapi tersedia untuk lingkup global seperti yay
helper:
yay() { eval $(yay_parse "$@"); }
Data input awalnya diproses dengan sed
. Ini menghapus garis yang tidak cocok dengan spesifikasi format Yamlesque sebelum membatasi bidang Yamlesque yang valid dengan karakter Pemisah File ASCII dan menghapus tanda kutip ganda di sekitar bidang nilai.
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" |
Kedua ungkapan itu serupa; mereka berbeda hanya karena yang pertama mengambil nilai yang dikutip sedangkan yang kedua memilih yang tidak dikutip.
The Berkas Separator (28 / hex 12 / oktal 034) digunakan karena, sebagai karakter non-printable, itu tidak mungkin dalam data masukan.
Hasilnya disalurkan ke awk
mana memproses inputnya satu baris pada satu waktu. Ini menggunakan karakter FS untuk menetapkan setiap bidang ke variabel:
indent = length($1)/2;
key = $2;
value = $3;
Semua baris memiliki indent (mungkin nol) dan kunci tetapi mereka tidak semua memiliki nilai. Ini menghitung tingkat indentasi untuk garis yang membagi panjang bidang pertama, yang berisi spasi putih terkemuka, sebanyak dua. Item tingkat atas tanpa indentasi berada pada level indent nol.
Selanjutnya, ia mengetahui apa yang prefix
harus digunakan untuk item saat ini. Inilah yang akan ditambahkan ke nama kunci untuk membuat nama array. Ada untuk root_prefix
larik level atas yang didefinisikan sebagai nama kumpulan data dan garis bawah:
root_prefix = "'$prefix'_";
if (indent ==0 ) {
prefix = ""; parent_key = "'$prefix'";
} else {
prefix = root_prefix; parent_key = keys[indent-1];
}
Ini parent_key
adalah kunci pada level indent di atas level indentasi baris saat ini dan mewakili koleksi yang menjadi bagian dari baris saat ini. Pasangan kunci / nilai koleksi akan disimpan dalam array dengan namanya didefinisikan sebagai gabungan dari prefix
dan parent_key
.
Untuk tingkat atas (tingkat indentasi nol) awalan kumpulan data digunakan sebagai kunci induk sehingga tidak memiliki awalan (diatur ke ""
). Semua array lainnya diawali dengan awalan root.
Selanjutnya, kunci saat ini dimasukkan ke dalam array (awk-internal) yang berisi kunci. Array ini tetap ada sepanjang seluruh sesi awk dan karenanya berisi kunci yang dimasukkan oleh baris sebelumnya. Kunci tersebut dimasukkan ke dalam array menggunakan indentasinya sebagai indeks array.
keys[indent] = key;
Karena array ini berisi kunci dari baris sebelumnya, kunci apa pun dengan parutan level indentasi dari level indentasi baris saat ini dihapus:
for (i in keys) {if (i > indent) {delete keys[i]}}
Ini meninggalkan array kunci yang berisi gantungan kunci dari root pada level indent 0 ke baris saat ini. Ini menghapus kunci basi yang tersisa ketika baris sebelumnya indentasi lebih dalam dari garis saat ini.
Bagian terakhir menampilkan bash
perintah: baris input tanpa nilai memulai level indent baru ( koleksi dalam bahasa YAML) dan baris input dengan nilai menambahkan kunci ke koleksi saat ini.
Nama koleksi adalah gabungan dari baris saat ini prefix
dan parent_key
.
Ketika kunci memiliki nilai, kunci dengan nilai itu ditetapkan untuk koleksi saat ini seperti ini:
printf("%s%s[%s]=\"%s\";\n", prefix, parent_key , key, value);
printf("%s%s[keys]+=\" %s\";\n", prefix, parent_key , key);
Pernyataan pertama menghasilkan perintah untuk menetapkan nilai ke elemen array asosiatif yang dinamai setelah kunci dan yang kedua mengeluarkan perintah untuk menambahkan kunci ke keys
daftar dibatasi-ruang koleksi :
<current_collection>[<key>]="<value>";
<current_collection>[keys]+=" <key>";
Ketika kunci tidak memiliki nilai, koleksi baru dimulai seperti ini:
printf("%s%s[children]+=\" %s%s\";\n", prefix, parent_key , root_prefix, key);
printf("declare -g -A %s%s;\n", root_prefix, key);
Pernyataan pertama menghasilkan perintah untuk menambahkan koleksi baru ke children
daftar terbatas-ruang koleksi saat ini dan yang kedua menampilkan perintah untuk mendeklarasikan array asosiatif baru untuk koleksi baru:
<current_collection>[children]+=" <new_collection>"
declare -g -A <new_collection>;
Semua output dari yay_parse
dapat diurai sebagai perintah bash oleh perintah bash eval
atau source
built-in.