将 JSON 文档中的文件名替换为该文件的内容

将 JSON 文档中的文件名替换为该文件的内容

我的目标:使用命令sedawk其他方式将文件名替换为 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

您可以在 github 上找到包含此脚本的项目。

答案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

使用 pythonwhich有一个模块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 perldscman perlreftut了解 perl 数据结构的详细信息)与 json 非常相似...或者,至少,它们之间有相当直接的翻译。

处理后,它看起来像:

{
  tests => {
    andthis     => { foo => "blabla\nblabla\n" },
    commands    => { value => "blabla\nblabla\n" },
    includethis => "blabla\nblabla\n",
  },
}

真正的 json 文件将包含更多数据并且更复杂。


顺便说一句,在 Debian 上您可以安装Data::Dump和.它们也应该作为大多数其他发行版的软件包提供。否则,请使用.JSONsudo apt install libjson-perl libdata-dump-perlcpan

答案4

sed '/{{/!b;h
s/.*<\|>.*//g
s/.*/cat &/e
s/\n/\\n/g
x;s/{.*//
G;s/\n//
s/$/"/' file.json

相关内容