Bash 创建 JSON 文件清单
Bash to create a JSON File Manifest
我有一个 bash 脚本来输出带有 MD5 哈希值的文件清单作为 JSON,如下所示:
{
"files": [
{
"md5": "f30ae4b2e0d2551b5962995426be0c3a",
"path": "assets/asset_1.png"
},
{
"md5": "ca8492fdc3547af31afeeb8656619ef0",
"path": "assets/asset_2.png"
},
]
}
它将return一个除.gdz之外的所有文件的列表。
我使用的命令是:
echo "{\"files\": [$(find . -type f -print | grep -v \.gdz$ | xargs md5sum | sed 's/\.\///' | xargs printf "{\"md5\": \"%s\", \"name\": \"%s\"}," | sed 's/,$//')]}" > files.json
但是,当我 运行 在生产中使用它时,它有时会切换 MD5 散列和文件路径。我不知道这是为什么,有人知道吗?
您可以考虑使用 bash
和 GNU 工具 find
和 md5sum
尝试这个。该脚本使用以 NUL 结尾的路径名并转义相关字符。即使文件名包含换行符,它也应该工作。
#!/bin/bash
comma=
printf '{\n "files": [\n'
while IFS= read -d '' -r line; do
md5=${line:0:32}
path=${line:34}
path=${path//'\'/'\'}
path=${path//'"'/'\"'}
path=${path//$'\b'/'\b'}
path=${path//$'\f'/'\f'}
path=${path//$'\n'/'\n'}
path=${path//$'\r'/'\r'}
path=${path//$'\t'/'\t'}
printf '%s%4s{\n%6s"md5": "%s",\n%6s"path": "%s"\n%4s}' \
"$comma" '' '' "${md5}" '' "${path}" ''
comma=$',\n'
done < <(find . -type f ! -name '*.gdz' -exec md5sum -z {} +)
printf '\n ]\n}\n'
或者,也使用 GNU sed
(这个版本可能更快):
#!/bin/bash
printf '{\n "files": ['
find . -type f ! -name '*.gdz' -exec md5sum -z {} + |
sed -Ez '
s/\/\\/g
s/"/\"/g
s/\x08/\b/g
s/\f/\f/g
s/\n/\n/g
s/\r/\r/g
s/\t/\t/g
s/(.{32})..(.*)/\
{\
"md5": "",\
"path": ""\
}/
$!s/$/,/' | tr -d '[=11=]'
printf '\n ]\n}\n'
我相信这两个脚本都很健壮,假设有最新的 GNU 工具。
在 shell 中稳健地执行此操作有点痛苦;你必须担心文件名中的空格(这会破坏你当前的代码),正确编码和转义你的 JSON 字符串(如果你有一个文件名中包含引号怎么办?)等
一个快速的 perl
脚本,执行相同的操作,将要扫描的目录作为命令行参数传递:
#!/usr/bin/env perl
use warnings;
use strict;
use File::Find;
use Digest::MD5;
use JSON::PP; # Or JSON::XS if installed
my @hashes;
find(\&wanted, @ARGV);
print JSON::PP->new->ascii->encode({files => \@hashes});
sub wanted {
if (-f $_ && $_ !~ /\.gdz$/) {
my $name = $File::Find::name;
$name =~ s!^\./!!;
open my $f, "<:raw", $_ or
die "Couldn't open $name: $!\n";
push @hashes, { path => $name,
md5 => Digest::MD5->new()->addfile($f)->hexdigest
};
close $f;
}
}
你可以 运行 md5sum
所有匹配的文件,然后用 jq 做剩下的:
find . -type f -not -name '*.gdz' -exec md5sum -z {} + \
| jq --slurp --raw-input '
{
files: split("\u0000")
| map(split(" "))
| map([
.[0],
(.[2:] | join(" "))
])
| map({md5: .[0], path: .[1]})
}'
find
命令的输出是 运行ning md5sum
在所有匹配文件上一次的输出,输出记录由空字节分隔。
然后 jq 执行以下操作(并且几乎肯定可以优化):
--slurp
和 --raw-input
在任何处理之前读取整个输入
- 在最外层,我们构建一个以
files
为键的对象
split("\u0000")
从空字节分隔的输入记录创建一个数组
map(split(" "))
将每个数组元素转换为按空格拆分的数组
map([ .[0], (.[2:] | join(" ")) ])
– 为了允许文件名中有空格,我们为每条记录创建一个数组,其中第一个元素是 md5 哈希值,第二个元素是其余元素的串联,即文件名; [2:]
因为我们要跳过两个空格
map({md5: .[0], path: .[1]})
将每个二元素数组转换为具有所需键的对象
尝试使用并非专为它设计的工具来创建 JSON 是一项非常容易出错的任务。请使用专用工具正确创建您想要的JSON。我强烈推荐 xidel:
xidel -se '
{
"files":array{
for $x in file:list(.,true())[not(ends-with(.,"/")) and not(ends-with(.,"gdz"))]
return {
"md5":substring(system(x"md5sum {$x}"),1,32),
"path":$x
}
}
}
'
file:list(.,true())
returns 当前目录中的所有文件和目录(并且将可选参数 $recursive
设置为 true()
所有后代目录也包括在内)。
[not(ends-with(.,"/")) and not(ends-with(.,"gdz"))]
通过删除目录和“gdz”文件过滤 file:list()
的输出。
system(x"md5sum {$x}")
returns md5sum
其标准输出结果为字符串(并且 substring(..,1,32)
显然 returns 前 32 个字符)。
x"..{..}.."
是一个扩展字符串,其中 x"There are {1+2+3} elements"
例如计算为“有 6 个元素”。
我有一个 bash 脚本来输出带有 MD5 哈希值的文件清单作为 JSON,如下所示:
{
"files": [
{
"md5": "f30ae4b2e0d2551b5962995426be0c3a",
"path": "assets/asset_1.png"
},
{
"md5": "ca8492fdc3547af31afeeb8656619ef0",
"path": "assets/asset_2.png"
},
]
}
它将return一个除.gdz之外的所有文件的列表。
我使用的命令是:
echo "{\"files\": [$(find . -type f -print | grep -v \.gdz$ | xargs md5sum | sed 's/\.\///' | xargs printf "{\"md5\": \"%s\", \"name\": \"%s\"}," | sed 's/,$//')]}" > files.json
但是,当我 运行 在生产中使用它时,它有时会切换 MD5 散列和文件路径。我不知道这是为什么,有人知道吗?
您可以考虑使用 bash
和 GNU 工具 find
和 md5sum
尝试这个。该脚本使用以 NUL 结尾的路径名并转义相关字符。即使文件名包含换行符,它也应该工作。
#!/bin/bash
comma=
printf '{\n "files": [\n'
while IFS= read -d '' -r line; do
md5=${line:0:32}
path=${line:34}
path=${path//'\'/'\'}
path=${path//'"'/'\"'}
path=${path//$'\b'/'\b'}
path=${path//$'\f'/'\f'}
path=${path//$'\n'/'\n'}
path=${path//$'\r'/'\r'}
path=${path//$'\t'/'\t'}
printf '%s%4s{\n%6s"md5": "%s",\n%6s"path": "%s"\n%4s}' \
"$comma" '' '' "${md5}" '' "${path}" ''
comma=$',\n'
done < <(find . -type f ! -name '*.gdz' -exec md5sum -z {} +)
printf '\n ]\n}\n'
或者,也使用 GNU sed
(这个版本可能更快):
#!/bin/bash
printf '{\n "files": ['
find . -type f ! -name '*.gdz' -exec md5sum -z {} + |
sed -Ez '
s/\/\\/g
s/"/\"/g
s/\x08/\b/g
s/\f/\f/g
s/\n/\n/g
s/\r/\r/g
s/\t/\t/g
s/(.{32})..(.*)/\
{\
"md5": "",\
"path": ""\
}/
$!s/$/,/' | tr -d '[=11=]'
printf '\n ]\n}\n'
我相信这两个脚本都很健壮,假设有最新的 GNU 工具。
在 shell 中稳健地执行此操作有点痛苦;你必须担心文件名中的空格(这会破坏你当前的代码),正确编码和转义你的 JSON 字符串(如果你有一个文件名中包含引号怎么办?)等
一个快速的 perl
脚本,执行相同的操作,将要扫描的目录作为命令行参数传递:
#!/usr/bin/env perl
use warnings;
use strict;
use File::Find;
use Digest::MD5;
use JSON::PP; # Or JSON::XS if installed
my @hashes;
find(\&wanted, @ARGV);
print JSON::PP->new->ascii->encode({files => \@hashes});
sub wanted {
if (-f $_ && $_ !~ /\.gdz$/) {
my $name = $File::Find::name;
$name =~ s!^\./!!;
open my $f, "<:raw", $_ or
die "Couldn't open $name: $!\n";
push @hashes, { path => $name,
md5 => Digest::MD5->new()->addfile($f)->hexdigest
};
close $f;
}
}
你可以 运行 md5sum
所有匹配的文件,然后用 jq 做剩下的:
find . -type f -not -name '*.gdz' -exec md5sum -z {} + \
| jq --slurp --raw-input '
{
files: split("\u0000")
| map(split(" "))
| map([
.[0],
(.[2:] | join(" "))
])
| map({md5: .[0], path: .[1]})
}'
find
命令的输出是 运行ning md5sum
在所有匹配文件上一次的输出,输出记录由空字节分隔。
然后 jq 执行以下操作(并且几乎肯定可以优化):
--slurp
和--raw-input
在任何处理之前读取整个输入- 在最外层,我们构建一个以
files
为键的对象 split("\u0000")
从空字节分隔的输入记录创建一个数组map(split(" "))
将每个数组元素转换为按空格拆分的数组map([ .[0], (.[2:] | join(" ")) ])
– 为了允许文件名中有空格,我们为每条记录创建一个数组,其中第一个元素是 md5 哈希值,第二个元素是其余元素的串联,即文件名;[2:]
因为我们要跳过两个空格map({md5: .[0], path: .[1]})
将每个二元素数组转换为具有所需键的对象
尝试使用并非专为它设计的工具来创建 JSON 是一项非常容易出错的任务。请使用专用工具正确创建您想要的JSON。我强烈推荐 xidel:
xidel -se '
{
"files":array{
for $x in file:list(.,true())[not(ends-with(.,"/")) and not(ends-with(.,"gdz"))]
return {
"md5":substring(system(x"md5sum {$x}"),1,32),
"path":$x
}
}
}
'
file:list(.,true())
returns 当前目录中的所有文件和目录(并且将可选参数$recursive
设置为true()
所有后代目录也包括在内)。[not(ends-with(.,"/")) and not(ends-with(.,"gdz"))]
通过删除目录和“gdz”文件过滤file:list()
的输出。system(x"md5sum {$x}")
returnsmd5sum
其标准输出结果为字符串(并且substring(..,1,32)
显然 returns 前 32 个字符)。
x"..{..}.."
是一个扩展字符串,其中x"There are {1+2+3} elements"
例如计算为“有 6 个元素”。