我的目标:使用命令sed
或awk
其他方式将文件名替换为 JSON 文件中的内容...
一个例子:
- 要修改的 JSON 文件 (
file.json
)
键在文档结构中... "value": "{{<test/myData.txt>}}" ...
value
的位置。.tests[].commands[].value
- 数据源文件 (
test/myData.txt
)blabla blabla
- 期望的结果 (
result.json
)... "value": "blabla\nblabla" ...
我的问题:
我尝试过sed
:
sed -E "s/\{\{<([^>]+)>\}\}/{r \1}/" file.json > result.json
但文件没有被读取,我得到这个结果:
...
"value": "{r test/myData.txt}"
...
有一个解决我的问题的想法sed
(或更好的想法)吗?
解决方案:
太感谢了 !所有答案都有帮助,但我想在 GitHub actions 的默认环境中使用命令而不安装任何新工具。所以我在 sed 和 jq 之间进行选择,因为它们是默认安装的。 sed 不涵盖 json 文档中原始字符串的自动转换,因此从逻辑上讲我更喜欢使用 jq。
我用jq玩调试 jq 脚本。
这是最终的脚本:
#!/bin/bash
if [ $# -eq 0 ]; then
printf "Utilization:\n"
printf "$0 <FILE_INPUT> [[--output|-o] <FILE_OUTPUT>]\n"
printf "example : ./importFile.sh test/testImportFile.side -o aeff.side"
exit 1
fi
while [ $# -gt 0 ]; do
case $1 in
--output|-o)
output="${2}"
shift
;;
*)
input="${1}"
esac
shift
done
cp -p $input $output
while : ; do
cp -p $output "$output.tmp"
datafile=$(jq -r 'first(.tests[].commands[].value | select(startswith("{{<"))| select(endswith(">}}")) | ltrimstr("{{<") | rtrimstr(">}}"))' "$output.tmp")
#echo "datafile $datafile"
if [ -z "$datafile" ]; then
# echo NOT FOUND
break
elif [ -f "$datafile" ]; then
# echo FOUND
jq --arg data "$(cat "$datafile")" '(first(.tests[].commands[].value | select(startswith("{{<"))| select(endswith(">}}")))) |= $data' "$output.tmp" > $output
else
printf 'Could not find "%s" referenced by "%s"\n' "$datafile" $input >&2
exit 1
fi
done
rm "$output.tmp"
echo DONE
答案1
这样做会遇到问题,sed
因为您需要解析文档、解码 JSON 文件中存储的路径名(它可能具有 JSON 编码的某些字符)以及对文件内容进行编码以包含到 JSON 文档中。这肯定是可行的使用sed
,它只是意味着您必须在 中实现 JSON 解析器sed
。
让我们使用现有的 JSON 感知工具,例如jq
。
由于我们在问题中看不到太多文件,因此我假设该文件看起来像
{
"description": "hello world example",
"value": "{{<test/myData.txt>}}"
}
或同等的
{"description":"hello world example","value":"{{<test/myData.txt>}}"}
即,该value
键是 JSON 文件中的顶级键之一。
我们在这里要做的是从和value
之间的键中解析出值,并将整个值替换为与我们剩下的路径名对应的文件的值。{{<
>}}
路径名可以jq
使用
jq -r '.value | ltrimstr("{{<") | rtrimstr(">}}")' file.json
这会删除侧翼{{<
并>}}
返回解码后的字符串值。
我们可以将此字符串放入 shell 变量中,如下所示:
datafile=$( jq -r '.value | ltrimstr("{{<") | rtrimstr(">}}")' file.json )
或者我们可以jq
创建一个在 shell 中计算的赋值语句(这将允许路径名以换行符结尾),
eval "$( jq -r '.value | ltrimstr("{{<") | rtrimstr(">}}") | @sh "datafile=\(.)"' file.json )"
该@sh
运算符确保我们从 JSON 文件解析的值被安全地引用给 shell。对于我的示例 JSON 文档,这将是eval
字符串datafile='test/myData.txt'
.
然后只需获取文件的数据并更新原始文件中该键的值即可:
jq --arg data "$(cat "$datafile")" '.value |= $data' file.json
这将创建一个包含文件的 JSON 编码数据的jq
变量。$data
数据用于更新value
键的值。
test/myData.txt
给出我的小示例文件和您的示例文件的结果:
{
"description": "hello world example",
"value": "blabla\nblabla"
}
如果您愿意,然后重定向到新文件名。
概括:
datafile=$( jq -r '.value | ltrimstr("{{<") | rtrimstr(">}}")' file.json )
jq --arg data "$(cat "$datafile")" '.value |= $data' file.json >result.json
添加健全性检查和诊断消息:
datafile=$( jq -r '.value | ltrimstr("{{<") | rtrimstr(">}}")' file.json )
if [ -f "$datafile" ]; then
jq --arg data "$(cat "$datafile")" '.value |= $data' file.json >result.json
else
printf 'Could not find "%s" referenced by "%s"\n' "$datafile" file.json >&2
fi
答案2
使用 python
which有一个模块json
来处理json数据。
python3 -c 'import re, sys, json
jfile,outfile = sys.argv[1:]
regex,rs = re.compile(r"^\{\{<.*>\}\}$"),"\n"
with open(jfile) as f:
d = json.load(f)
for el in d["tests"]:
for lod in el["commands"]:
if re.search(regex,lod["value"]):
txtfile = re.sub(r"^\{\{<|>\}\}$","",lod["value"])
with open(txtfile) as t:
contents = "".join(t.readlines()).rstrip(rs)
break
else:
continue
break
for el in d["tests"]:
for lod in el["commands"]:
lod["value"] = contents
with open(outfile,"w") as w:
json.dump(d,w,indent=2)
' file.json result.json
答案3
这是一个非常基本的 Perl 示例,使用JSON库模块。
该脚本将递归地迭代全部json 数据的键,替换与文件包含正则表达式 ( ) 匹配的所有键,\{\{<([^>]*)>\}\}/
并将该键的值替换为文件的内容。
#!/usr/bin/perl
use strict;
use JSON;
use Data::Dump qw(dd);
local $/; # read entire files at once
my $text = <>; # slurp file.json into $text
my $json = JSON->new->canonical; # canonical causes the keys to be sorted
my $j = $json->decode($text);
#dd $j;
process_includes($j);
#dd $j;
print $json->pretty->encode($j);
sub process_includes {
# This subroutine recursively iterates through all the
# keys, replacing values which match {{<filename>}}
# with the contents of "filename".
my $h = shift; # expects a hashref containing json data
foreach my $key (keys %$h) {
if ($h->{$key} =~ m/\{\{<([^>]*)>\}\}/) {
# we have a match, slurp in the file and apply it.
my $file = $1;
# read the file
open(my $fh,"<",$file) or die "couldn't open '$file': $!\n";
my $contents = <$fh>;
close($fh);
# replace the value with the file contents
$h->{$key} = $contents;
} elsif (ref($h->{$key}) eq "HASH") {
# we have a hashref, so recurse into it.
process_includes($h->{$key});
};
}
}
将其另存为,例如,json-include.pl
并使用 使其可执行chmod +x json-include.pl
,然后运行它:
$ ./json-include.pl file2.json
{
"tests" : {
"andthis" : {
"foo" : "blabla\nblabla\n"
},
"commands" : {
"value" : "blabla\nblabla\n"
},
"includethis" : "blabla\nblabla\n"
}
}
file2.json
包含:
$ cat file2.json
{
"tests" : {
"commands" : {
"value" : "{{<test/myData.txt>}}"
},
"includethis" : "{{<test/myData.txt>}}",
"andthis" : {
"foo" : "{{<test/myData.txt>}}"
}
}
}
注意:我在上面每次都使用相同的文件名,但是您可以使用您喜欢的任何文件名,只要它存在并包含有效数据即可。文件名可以是绝对路径名,也可以是相对于当前目录的路径名。
你可以使用perl的数据::转储$j
模块在解码后获取格式良好的转储,这将向您展示 json 数据作为 Perl 对象的样子。这将使您更容易找到要使用的键(并且对于调试也很有用)。我在代码中留下了注释掉的示例。
对于上面的 file2.json,输出前处理process_includes()
看起来像:
{
tests => {
andthis => { foo => "{{<test/myData.txt>}}" },
commands => { value => "{{<test/myData.txt>}}" },
includethis => "{{<test/myData.txt>}}",
},
}
顺便说一句,很明显,这与 json 数据文件并不完全相似 - perl Hashes-of-Hashes(HoH,请参阅man perldsc
和man perlreftut
了解 perl 数据结构的详细信息)与 json 非常相似...或者,至少,它们之间有相当直接的翻译。
处理后,它看起来像:
{
tests => {
andthis => { foo => "blabla\nblabla\n" },
commands => { value => "blabla\nblabla\n" },
includethis => "blabla\nblabla\n",
},
}
真正的 json 文件将包含更多数据并且更复杂。
顺便说一句,在 Debian 上您可以安装Data::Dump
和.它们也应该作为大多数其他发行版的软件包提供。否则,请使用.JSON
sudo apt install libjson-perl libdata-dump-perl
cpan
答案4
sed '/{{/!b;h
s/.*<\|>.*//g
s/.*/cat &/e
s/\n/\\n/g
x;s/{.*//
G;s/\n//
s/$/"/' file.json