技術に詳しくないユーザーでもできるだけ簡単に編集できる構造化された構成ファイルを提供したいので(残念ながらファイルでなければなりません)、YAMLを使用したいと考えました。ただし、これをUnixシェルスクリプトから解析する方法は見つかりません。
私のユースケースは、この元の投稿が求めていたものとまったく同じ場合もそうでない場合もありますが、間違いなく似ています。
いくつかのYAMLをbash変数として取り込む必要があります。 YAMLの深さが1レベルを超えることはありません。
YAMLは次のようになります。
KEY: value
ANOTHER_KEY: another_value
OH_MY_SO_MANY_KEYS: yet_another_value
LAST_KEY: last_value
次のような出力:
KEY="value"
ANOTHER_KEY="another_value"
OH_MY_SO_MANY_KEYS="yet_another_value"
LAST_KEY="last_value"
次の行で出力を達成しました。
sed -e 's/:[^:\/\/]/="/g;s/$/"/g;s/ *=/=/g' file.yaml > file.sh
s/:[^:\/\/]/="/g
は:
を見つけて="
に置き換えますが、://
は無視します(URLの場合)s/$/"/g
は各行の末尾に"
を追加しますs/ *=/=/g
は、=
の前のすべてのスペースを削除します以下は、sedとawkを利用して単純なyamlファイルを解析するbash専用のパーサーです。
function parse_yaml {
local prefix=$2
local s='[[:space:]]*' w='[a-zA-Z0-9_]*' fs=$(echo @|tr @ '\034')
sed -ne "s|^\($s\):|\1|" \
-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" $1 |
awk -F$fs '{
indent = length($1)/2;
vname[indent] = $2;
for (i in vname) {if (i > indent) {delete vname[i]}}
if (length($3) > 0) {
vn=""; for (i=0; i<indent; i++) {vn=(vn)(vname[i])("_")}
printf("%s%s%s=\"%s\"\n", "'$prefix'",vn, $2, $3);
}
}'
}
次のようなファイルを理解します。
## global definitions
global:
debug: yes
verbose: no
debugging:
detailed: no
header: "debugging started"
## output
output:
file: "yes"
以下を使用して解析する場合:
parse_yaml sample.yml
出力されます:
global_debug="yes"
global_verbose="no"
global_debugging_detailed="no"
global_debugging_header="debugging started"
output_file="yes"
また、Rubyシンボルを含むRubyによって生成されるyamlファイルも理解します。
---
:global:
:debug: 'yes'
:verbose: 'no'
:debugging:
:detailed: 'no'
:header: debugging started
:output: 'yes'
前の例と同じ結果が出力されます。
スクリプト内での一般的な使用法は次のとおりです。
eval $(parse_yaml sample.yml)
parse_yamlはプレフィックス引数を受け入れるため、インポートされた設定はすべて共通のプレフィックスを持ちます(名前空間の衝突のリスクを軽減します)。
parse_yaml sample.yml "CONF_"
収量:
CONF_global_debug="yes"
CONF_global_verbose="no"
CONF_global_debugging_detailed="no"
CONF_global_debugging_header="debugging started"
CONF_output_file="yes"
ファイル内の以前の設定は、後の設定で参照できることに注意してください。
## global definitions
global:
debug: yes
verbose: no
debugging:
detailed: no
header: "debugging started"
## output
output:
debug: $global_debug
もう1つの素晴らしい使用法は、最初にデフォルトファイルを解析し、次にユーザー設定を解析することです。後者の設定は最初の設定を上書きするため、動作します。
eval $(parse_yaml defaults.yml)
eval $(parse_yaml project.yml)
シェルコマンドラインからのYAMLクエリニーズのために、pythonにshyaml
を記述しました。
概要:
$ pip install shyaml ## installation
例のYAMLファイル(複雑な機能を含む):
$ cat <<EOF > test.yaml
name: "MyName !!"
subvalue:
how-much: 1.1
things:
- first
- second
- third
other-things: [a, b, c]
maintainer: "Valentin Lab"
description: |
Multiline description:
Line 1
Line 2
EOF
基本的なクエリ:
$ cat test.yaml | shyaml get-value subvalue.maintainer
Valentin Lab
複雑な値に対するより複雑なループクエリ:
$ cat test.yaml | shyaml values-0 | \
while read -r -d $'\0' value; do
echo "RECEIVED: '$value'"
done
RECEIVED: '1.1'
RECEIVED: '- first
- second
- third'
RECEIVED: '2'
RECEIVED: 'Valentin Lab'
RECEIVED: 'Multiline description:
Line 1
Line 2'
いくつかの重要なポイント:
\0
埋め込み出力は、複数行のソリッドエントリ操作に使用できます。subvalue.maintainer
は有効なキーです)。subvalue.things.-1
はsubvalue.things
シーケンスの最後の要素です)。より多くのサンプルとドキュメントが shyaml githubページ または shyaml PyPIページ で利用可能です。
Pythonなどの一部のインタープリターに小さなスクリプトを渡すことができます。 RubyとそのYAMLライブラリを使用して簡単に行う方法は次のとおりです。
$ Ruby_SCRIPT="data = YAML::load(STDIN.read); puts data['a']; puts data['b']"
$ echo -e '---\na: 1234\nb: 4321' | Ruby -ryaml -e "$Ruby_SCRIPT"
1234
4321
、wheredata
は、yamlの値を持つハッシュ(または配列)です。
ボーナスとして、解析されます Jekyllの前件 まったく問題ありません。
Ruby -ryaml -e "puts YAML::load(open(ARGV.first).read)['tags']" example.md
Python3とPyYAMLが今日満たすのに非常に簡単な依存関係であることを考えると、以下が役に立つかもしれません:
yaml() {
python3 -c "import yaml;print(yaml.load(open('$1'))$2)"
}
VALUE=$(yaml ~/my_yaml_file.yaml "['a_key']")
yq は軽量でポータブルなコマンドラインYAMLプロセッサです
プロジェクトの目的は、yamlファイルの jq またはsedにすることです。
( http://mikefarah.github.io/yq/ )
例として( documentation から直接盗まれた)、次のsample.yamlファイルが与えられた場合:
---
bob:
item1:
cats: bananas
item2:
cats: apples
それから
yq r sample.yaml bob.*.cats
出力します
- bananas
- apples
パーサーがYAMLドキュメントから何を抽出するかによって異なるため、言うのは難しいです。単純な場合、grep
、cut
、awk
などを使用できる場合があります。より複雑な解析を行うには、Pythonの PyYAML または YAML: :Perl 。
Yay!(YamlはYamlesqueではありません!)と呼ばれるパーサーを作成し、Yamlesque、YAMLの小さなサブセット。したがって、Bashの100%準拠のYAMLパーサーを探している場合、これはそうではありません。しかし、OPを引用するために、もしあなたがYAMLのような非技術的なユーザーが編集するのが可能な限り簡単な構造化された設定ファイルを望むなら、これは興味。
以前の回答では説明されていません ですが、連想配列を書き込みます(はい、基本変数の代わりにBash 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;"
配列宣言は、Bashバージョン4の機能である連想(-A
)であることに注意してください。宣言もグローバル(-g
)であるため、関数で実行できますが、yay
ヘルパーのようなグローバルスコープで使用できます。
yay() { eval $(yay_parse "$@"); }
入力データは、最初にsed
で処理されます。有効なヤムレスクフィールドをASCII File Separator 文字で区切り、値フィールドを囲む二重引用符を削除する前に、ヤムレスク形式の仕様に一致しない行を削除します。
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" |
2つの式は似ています。違いは、最初の値が引用された値を選択し、2番目の値が引用されていない値を選択するためです。
File Separator (28/hex 12/octal 034)が使用されるのは、印刷できない文字として、入力データに含まれている可能性が低いためです。
結果はawk
にパイプされ、入力は一度に1行ずつ処理されます。 FS 文字を使用して、各フィールドを変数に割り当てます。
indent = length($1)/2;
key = $2;
value = $3;
すべての行にはインデント(場合によってはゼロ)とキーがありますが、すべての行に値はありません。先頭の空白を含む最初のフィールドの長さを2で割る行のインデントレベルを計算します。インデントのない最上位のアイテムは、インデントレベル0です。
次に、現在のアイテムに使用する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
の連結として定義された名前を持つ配列に格納されます。
最上位(インデントレベル0)では、データセットプレフィックスが親キーとして使用されるため、プレフィックスはありません(""
に設定されます)。他のすべての配列には、ルートプレフィックスがプレフィックスとして付けられます。
次に、現在のキーがキーを含む(awk-internal)配列に挿入されます。この配列はawkセッション全体を通じて持続するため、前の行で挿入されたキーが含まれます。キーは、インデントを配列インデックスとして使用して配列に挿入されます。
keys[indent] = key;
この配列には前の行のキーが含まれているため、現在の行のインデントレベルよりも大きいインデントレベルを持つキーは削除されます。
for (i in keys) {if (i > indent) {delete keys[i]}}
これにより、インデントレベル0のルートから現在の行までのキーチェーンを含むキー配列が残ります。前の行が現在の行よりも深くインデントされたときに残っている古いキーを削除します。
最後のセクションはbash
コマンドを出力します:値のない入力行は新しいインデントレベル(YAML用語ではcollection)を開始し、値のある入力行はキーを追加します現在のコレクション。
コレクションの名前は、現在の行のprefix
とparent_key
を連結したものです。
キーに値がある場合、その値を持つキーは次のように現在のコレクションに割り当てられます。
printf("%s%s[%s]=\"%s\";\n", prefix, parent_key , key, value);
printf("%s%s[keys]+=\" %s\";\n", prefix, parent_key , key);
最初のステートメントは、キーにちなんだ名前の連想配列要素に値を割り当てるコマンドを出力し、2番目のステートメントは、コレクションのスペース区切りの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
リストに新しいコレクションを追加するコマンドを出力し、2番目のステートメントは、新しいコレクションの新しい連想配列を宣言するコマンドを出力します。
<current_collection>[children]+=" <new_collection>"
declare -g -A <new_collection>;
yay_parse
からのすべての出力は、bash eval
またはsource
組み込みコマンドによってbashコマンドとして解析できます。
ここにステファン・ファレスタムの答えの拡張版があります:
function parse_yaml {
local prefix=$2
local s='[[:space:]]*' w='[a-zA-Z0-9_]*' fs=$(echo @|tr @ '\034')
sed -ne "s|,$s\]$s\$|]|" \
-e ":1;s|^\($s\)\($w\)$s:$s\[$s\(.*\)$s,$s\(.*\)$s\]|\1\2: [\3]\n\1 - \4|;t1" \
-e "s|^\($s\)\($w\)$s:$s\[$s\(.*\)$s\]|\1\2:\n\1 - \3|;p" $1 | \
sed -ne "s|,$s}$s\$|}|" \
-e ":1;s|^\($s\)-$s{$s\(.*\)$s,$s\($w\)$s:$s\(.*\)$s}|\1- {\2}\n\1 \3: \4|;t1" \
-e "s|^\($s\)-$s{$s\(.*\)$s}|\1-\n\1 \2|;p" | \
sed -ne "s|^\($s\):|\1|" \
-e "s|^\($s\)-$s[\"']\(.*\)[\"']$s\$|\1$fs$fs\2|p" \
-e "s|^\($s\)-$s\(.*\)$s\$|\1$fs$fs\2|p" \
-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" | \
awk -F$fs '{
indent = length($1)/2;
vname[indent] = $2;
for (i in vname) {if (i > indent) {delete vname[i]; idx[i]=0}}
if(length($2)== 0){ vname[indent]= ++idx[indent] };
if (length($3) > 0) {
vn=""; for (i=0; i<indent; i++) { vn=(vn)(vname[i])("_")}
printf("%s%s%s=\"%s\"\n", "'$prefix'",vn, vname[indent], $3);
}
}'
}
このバージョンは、-
表記と、辞書とリストの短い表記をサポートしています。次の入力:
global:
input:
- "main.c"
- "main.h"
flags: [ "-O3", "-fpic" ]
sample_input:
- { property1: value, property2: "value2" }
- { property1: "value3", property2: 'value 4' }
この出力を生成します:
global_input_1="main.c"
global_input_2="main.h"
global_flags_1="-O3"
global_flags_2="-fpic"
global_sample_input_1_property1="value"
global_sample_input_1_property2="value2"
global_sample_input_2_property1="value3"
global_sample_input_2_property2="value 4"
ご覧のとおり、-
アイテムには自動的に番号が付けられ、各アイテムに異なる変数名が付けられます。 bash
には多次元配列がないため、これを回避する方法の1つです。複数のレベルがサポートされています。 @briceburgで言及されている末尾の空白の問題を回避するには、値を一重引用符または二重引用符で囲む必要があります。ただし、まだいくつかの制限があります。辞書とリストを展開すると、値にコンマが含まれている場合に誤った結果が生じる可能性があります。また、複数行にわたる値(sshキーなど)などのより複雑な構造は(まだ)サポートされていません。
コードに関するいくつかの言葉:最初のsed
コマンドは、辞書の短い形式{ key: value, ...}
を通常の形式に拡張し、それらをより単純なyamlスタイルに変換します。 2番目のsed
呼び出しは、リストの短い表記に対して同じことを行い、[ entry, ... ]
を-
表記で項目化されたリストに変換します。 3番目のsed
呼び出しは、通常の辞書を処理した元の呼び出しであり、現在は-
とインデントを含むリストを処理するための追加があります。 awk
部分は各インデントレベルにインデックスを導入し、変数名が空の場合(つまり、リストを処理する場合)にインデックスを増やします。空のvnameの代わりに、カウンターの現在の値が使用されます。 1レベル上がると、カウンターはゼロになります。
編集:私は githubリポジトリ を作成しました。
別のオプションは、YAMLをJSONに変換し、jqを使用してJSON表現と対話し、JSON表現から情報を抽出するか編集することです。
この接着剤を含む簡単なbashスクリプトを作成しました- GitHubのY2Jプロジェクト を参照してください
Perl -ne 'chomp; printf qq/%s="%s"\n/, split(/\s*:\s*/,$_,2)' file.yml > file.sh
python 2とPyYAMLがある場合、 parse_yaml.py と呼ばれるこのパーサーを使用できます。洗練された機能のいくつかは、接頭辞を選択して(同様の変数を持つ複数のファイルがある場合)、yamlファイルから単一の値を選択できるようにすることです。
たとえば、これらのyamlファイルがある場合:
staging.yaml:
db:
type: sqllite
Host: 127.0.0.1
user: dev
password: password123
prod.yaml:
db:
type: postgres
Host: 10.0.50.100
user: postgres
password: password123
競合することなく両方をロードできます。
$ eval $(python parse_yaml.py prod.yaml --prefix prod --cap)
$ eval $(python parse_yaml.py staging.yaml --prefix stg --cap)
$ echo $PROD_DB_Host
10.0.50.100
$ echo $STG_DB_Host
127.0.0.1
そして、チェリーでさえあなたが望む値を選びます。
$ prod_user=$(python parse_yaml.py prod.yaml --get db_user)
$ prod_port=$(python parse_yaml.py prod.yaml --get db_port --default 5432)
$ echo prod_user
postgres
$ echo prod_port
5432
Golangで書かれた yq の equivalent を使用できます。
./go-yg -yamlFile /home/user/dev/ansible-firefox/defaults/main.yml -key
firefox_version
戻り値:
62.0.3
これは非常に具体的ですが、私の答えは特定のユーザーに役立つと思います。node
とnpm
がマシンにインストールされている場合、js-yaml
を使用できます。
最初のインストール:
npm i -g js-yaml
# or locally
npm i js-yaml
その後、あなたのbashスクリプトで
#!/bin/bash
js-yaml your-yaml-file.yml
また、jq
を使用している場合は、そのようなことを行うことができます
#!/bin/bash
json="$(js-yaml your-yaml-file.yml)"
aproperty="$(jq '.apropery' <<< "$json")"
echo "$aproperty"
js-yaml
はyamlファイルをjson文字列リテラルに変換するためです。その後、Unixシステム内の任意のjsonパーサーで文字列を使用できます。
Grunt (JavaScript Task Runner)の使用も検討できます。シェルと簡単に統合できます。 YAML(grunt.file.readYAML
)およびJSON(grunt.file.readJSON
)ファイルの読み取りをサポートしています。
これは、Gruntfile.js
(またはGruntfile.coffee
)でタスクを作成することで実現できます。例:
module.exports = function (grunt) {
grunt.registerTask('foo', ['load_yml']);
grunt.registerTask('load_yml', function () {
var data = grunt.file.readYAML('foo.yml');
Object.keys(data).forEach(function (g) {
// ... switch (g) { case 'my_key':
});
});
};
次に、Shellからgrunt foo
を実行するだけです(使用可能なタスクについてはgrunt --help
を確認してください)。
さらに、タスク(exec:foo
)から渡された入力変数を使用してgrunt-exec
タスク(foo: { cmd: 'echo bar <%= foo %>' }
)を実装し、任意の形式で出力を印刷し、別のコマンドにパイプすることができます。
Gruntに似たツールもあります。これは gulp と呼ばれ、追加のプラグイン gulp-yaml です。
経由でインストール:npm install --save-dev gulp-yaml
サンプル使用法:
var yaml = require('gulp-yaml');
gulp.src('./src/*.yml')
.pipe(yaml())
.pipe(gulp.dest('./dist/'))
gulp.src('./src/*.yml')
.pipe(yaml({ space: 2 }))
.pipe(gulp.dest('./dist/'))
gulp.src('./src/*.yml')
.pipe(yaml({ safe: true }))
.pipe(gulp.dest('./dist/'))
YAML形式 を処理するその他のオプションについては、利用可能なプロジェクト、ライブラリ、およびその形式の解析に役立つその他のリソースについて YAMLサイト を確認してください。
その他のツール:
jSONの解析、読み取り、作成