我正在学习bash
,我需要的是迭代一个目录(里面有其他目录)并找到名称为example.yaml
.
这些文件有几个键值对(示例如下):
name: Andre
age: 13
address: street
weight: 78kgs
我需要的是在某个目录(必须包括嵌套目录)内使用 bash 命令查找所有example.yaml
文件,然后仅将名称和年龄复制到新文件。需要创建这个新文件,如下所示:
persons:
- name: Andre
age: 13
- name: Joao
age: 18
...
我试图做这样的事情来解决这个问题
printf 'persons:\n' > output.yml
for i in $(find ./ -name "example.yaml");
do
name=$(yq '.name' $i)
age=$(yq '.age' $i)
// append $name and $age to output.yaml
done
答案1
注意:这个答案的长度是因为至少有两个主要的实用程序变体称为yq
,用于解析 YAML 数据,它们的功能和表达式语法略有不同,我涵盖了这两个变体。我还考虑简单地使用文件名通配来查找所有文件和使用find
(当输入文件太多时)。最后,我回答评论中提出的其他问题。
不要迭代 的输出find
。相反,请find
使用调用您的实用程序-exec
。我在这个答案的下面有一个例子。您还缺少对某些扩展的引用。
也可以看看:
在命令行上给定一个或多个 YAML 文件,以下yq
命令将创建 YAML 数据摘要文件:
yq -y -s '{ persons: map({ name: .name, age: .age }) }' files
该命令将所有输入读取到一个大数组中(感谢-s
, 或--slurp
),然后将其传递给该map()
命令。该map()
命令提取数组中每个元素的name
和字段,并将它们作为对象添加到数组中。age
persons
这使用 Andrey Kislyuk 的基于 Pythonyq
的https://kislyuk.github.io/yq/,多功能 JSON 解析器的包装器jq
。如果-y
从命令中删除该选项,您将获得 JSON 输出。
使用 Mike Farah 的基于 Go 的yq
替代方案:
yq -N '[{ "name": .name, "age": .age }]' files | yq '{ "persons": . }'
在bash
shell 中,您可以将其应用于example.yaml
当前目录或其下任何位置的所有文件,从而output.yaml
在当前目录中创建输出文件,如下所示:
shopt -s globstar failglob
yq -y -s '{ persons: map({ name: .name, age: .age }) }' ./**/example.yaml >output.yaml
或者,用迈克法拉的yq
:
shopt -s globstar failglob
yq -N '[{ "name": .name, "age": .age }]' ./**/example.yaml | yq '{ "persons": . }' >output.yaml
这假设文件少于几千个example.yaml
,或者命令行将扩展为太长的命令。
首先启用shellglobstar
选项以允许我们使用**
文件名通配模式,该模式在路径名中进行匹配/
。failglob
如果没有匹配的文件名,我们还启用shell 选项以使整个命令正常失败。
测试:
$ tree
.
├── dir1
│ └── example.yaml
├── example.yaml
└── script-andrey
└── script-mike
1 directory, 4 files
$ cat script-andrey
shopt -s globstar failglob
yq -y -s '{ persons: map({ name: .name, age: .age }) }' ./**/example.yaml >output.yaml
$ bash script-andrey
$ cat output.yaml
persons:
- name: Joao
age: 18
- name: Andre
age: 13
yq
还测试麦克风:
$ cat script-mike
shopt -s globstar failglob
yq -N '[{ "name": .name, "age": .age }]' ./**/example.yaml | yq '{ "persons": . }' >output.yaml
$ bash script-mike
$ cat output.yaml
persons:
- name: Joao
age: 18
- name: Andre
age: 13
如果您有成千上万个这样的 YAML 输入文件,那么您可能需要yq
更智能地应用,使用find
.
这是使用安德烈的yq
:
find . -name example.yaml -type f \
-exec yq -y -s 'map({ name: .name, age: .age })' {} + |
yq -y '{ persons: . }' >output.yaml
这会找到名称为 的所有常规文件example.yaml
。这些数据会分批传递,并从中yq
提取name
和age
字段,从而创建一个数组。然后是最后一个yq
命令,用于收集生成的 YAML 数组并将其作为persons
最终输出中的键值。
同样,迈克的yq
:
find . -name example.yaml -type f \
-exec yq -N '[{ "name": .name, "age": .age }]' {} + |
yq '{ "persons": . }' >output.yaml
使用与上面相同的文件集进行测试:
$ rm output.yaml
$ find . -name example.yaml -type f -exec yq -y -s 'map({ name: .name, age: .age })' {} + | yq -y '{ persons: . }' >output.yaml
$ cat output.yaml
persons:
- name: Andre
age: 13
- name: Joao
age: 18
(运行为 Mike 设计的命令yq
会生成相同的输出。)
find
请注意,输出的顺序取决于查找文件的顺序。
您是否想要对例如字段上的输出文件进行排序name
,那么以下内容将就地对文件进行排序(请注意,我不知道如何使用 Mike Farah 的基于 Go 来执行此操作yq
):
yq -i -y '.persons |= sort_by(.name)' output.yaml
要按相反顺序(就地)排序:
yq -i -y '.persons |= (sort_by(.name) | reverse)' output.yaml
在评论中,用户询问是否可以将数据附加到现有文件中。这个有可能。
下面的命令假设最后一个output.yaml
是数组的末尾persons
(这样该命令就可以向其中添加新的数组条目)。
使用安德烈的yq
:
shopt -s globstar failglob
yq -y -s 'map({ name: .name, age: .age })' ./**/example.yaml >>output.yaml
或者,与find
,
find . -name example.yaml -type f \
-exec yq -y -s 'map({ name: .name, age: .age })' {} + >>output.yaml
使用迈克的yq
:
shopt -s globstar failglob
yq -N '[{ "name": .name, "age": .age }]' ./**/example.yaml >>output.yaml
或者,使用find
:
find . -name example.yaml -type f \
-exec yq -N '[{ "name": .name, "age": .age }]' {} + >>output.yaml
答案2
有很多方法可以做到这一点,但最简单的可能是find
命令。
首先,我们使用新的数组结构创建输出文件:
echo "persons:" > newfile.yaml
接下来,我们要识别每一个文件与目标目录中的文件名匹配example.yaml
(我们称之为/home/user/yaml-files
)。这是 find 的基本用例,并且相当容易理解:
find /home/user/yaml-files -type f -name example.yaml
find
有一个强大的内置功能,可以在找到匹配项时使用-exec
和-execdir
选项执行 shell 命令。-exec
在运行时的同一工作目录中执行find
,而 while-execdir
是一个更安全的选项,因为 shell 命令运行“里面”找到匹配项的目录。为了简单起见,我们将使用-exec
.
我们需要example.yaml
在这些文件中搜索我们想要的行,重新格式化并将结果附加到我们的输出文件中:
find /home/user/yaml-files -type f -name example.yaml -exec awk '$1 ~ /^name:|^age:/ {gsub(/name:/," - name:",$1); gsub(/age:/," age:",$1); print $0}' {} \; | tee -a newfile.yaml
其中的命令awk
搜索example.yaml
以 或 开头的每行name:
,age:
前面没有空格或其他字符。gsub
是一个awk
内置函数,对于字符串替换很有用。这里我们有 2 个gsub
过滤器,在将匹配的行打印到stdout
.
通常,人们会使用重定向将输出写入文件,但find -exec
这样做确实会变得有点复杂。在这种情况下,该tee
命令很棒 - 它会将输出回显到控制台,也回显到文件。该-a
选项tee
告诉附加到文件,否则每次都会覆盖该文件,并且我们只会留下最后一次写入文件的结果。
该解决方案仅使用几个命令,据我所知,您可能遇到的每个 Linux 系统上都存在这些命令 - 没有特殊要求,并且代码非常可移植。
答案3
如果您正在查找具有特定名称的文件example.yaml
,则可以非常轻松地做到这一点。首先创建一个新文件persons:
,然后将所有以所有文件开头name:
或age:
来自所有example.yaml
文件的行附加到其中:
printf 'persons:\n' > personsFile
find /target/directory -name example.yaml -exec grep -E '^(name|age):' {} + >> personsFile
如果您确实需要-
每个条目前面的name
和缩进,您可以在第二遍中添加它:
printf 'persons:\n' > personsFile
find /target/directory -name example.yaml -exec grep -E '^(name|age):' {} + >> personsFile
sed -i 's/^name/ - name/; s/^age/ age/' personsFile
但如果您确实正在处理像 YAML 这样的结构化格式,您可能应该查看专用工具,而不是像这样破解它。
答案4
阅读man find xargs grep bash
并执行以下操作:
printf "%s\n" "persons:" >newfile
find . -type f -name '*.yaml' -print0 | \
xargs -0 -r \
grep -E --no-filename 'name:|age:' >>newfile
注意:此代码尚未经过测试。