Note: The length of this answer is due to the fact that there are at least two major variants of utilities called yq
, made for parsing YAML data, with slightly different abilities and expression grammar, and I cover both. I also look at simply using filename globbing to find all files and using find
(when there simply are too many input files). Finally, I address additional questions asked in the comments.
Don't iterate over the output of find
. Instead, call your utility from find
using -exec
. I have an example of this further down in this answer. You also lack quoting of some expansions.
See also:
Given one or several YAML files on the command line, the following yq
command would create the YAML data summary file:
yq -y -s '{ persons: map({ name: .name, age: .age }) }' files
The command reads all input into a large array (thanks to -s
, or --slurp
) which is then passed to the map()
command. The map()
command extracts the name
and age
fields of each element in the array and adds these as an object to the persons
array.
This uses Andrey Kislyuk's Python-based yq
from https://kislyuk.github.io/yq/, a wrapper around the versatile JSON parser jq
. If you drop the -y
option from the command, you'll get JSON output instead.
Using Mike Farah's Go-based yq
instead:
yq -N '[{ "name": .name, "age": .age }]' files | yq '{ "persons": . }'
In the bash
shell, you would apply this to all example.yaml
files in the current directory or anywhere below it, creating the output file output.yaml
in the current directory, like so:
shopt -s globstar failglob
yq -y -s '{ persons: map({ name: .name, age: .age }) }' ./**/example.yaml >output.yaml
Or, with Mike Farah's yq
:
shopt -s globstar failglob
yq -N '[{ "name": .name, "age": .age }]' ./**/example.yaml | yq '{ "persons": . }' >output.yaml
This assumes that there are fewer than a few thousand example.yaml
files, or the command line would expand to a too long command.
The globstar
shell option is first enabled to allow us to use the **
filename globbing pattern, which matches across /
in pathnames. We also enable the failglob
shell option to make the whole command fail gracefully if there are no matching filenames.
Testing:
$ 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
Testing Mikes yq
as well:
$ 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
If you have many many thousands of these YAML input files, then you may want to apply yq
a bit smarter, using find
.
This is using Andrey's yq
:
find . -name example.yaml -type f \
-exec yq -y -s 'map({ name: .name, age: .age })' {} + |
yq -y '{ persons: . }' >output.yaml
This finds all regular files whose name is example.yaml
. These are passed in batches to yq
which will extract the name
and age
fields from each, creating an array. There is then a final yq
command that collects the generated YAML array and places it as the value of the persons
key in the final output.
Similarly, with Mike's yq
:
find . -name example.yaml -type f \
-exec yq -N '[{ "name": .name, "age": .age }]' {} + |
yq '{ "persons": . }' >output.yaml
Testing with the same set of files as above:
$ 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
(Running the command designed for Mike's yq
generates the same output.)
Note that the ordering of the output depends on the order in which find
finds the files.
Would you want to sort the output file on e.g. the name
field, then the following would sort the file in-place (note that I don't know how to do this with Mike Farah's Go-based yq
):
yq -i -y '.persons |= sort_by(.name)' output.yaml
To sort (in-place) in the reverse order:
yq -i -y '.persons |= (sort_by(.name) | reverse)' output.yaml
In comments, the user asks whether one can just append data to an existing file. This is possible.
The commands below assume that the last thing in output.yaml
is the end of the persons
array (so that the command is able to just adds new array entries to it).
Using Andrey's yq
:
shopt -s globstar failglob
yq -y -s 'map({ name: .name, age: .age })' ./**/example.yaml >>output.yaml
or, with find
,
find . -name example.yaml -type f \
-exec yq -y -s 'map({ name: .name, age: .age })' {} + >>output.yaml
Using Mike's yq
:
shopt -s globstar failglob
yq -N '[{ "name": .name, "age": .age }]' ./**/example.yaml >>output.yaml
or, using find
:
find . -name example.yaml -type f \
-exec yq -N '[{ "name": .name, "age": .age }]' {} + >>output.yaml
example.yaml
?shopt -s globstar; for yamlfile in **/example.yaml; do some_specific_yamltool --options "${yamlfile}"; done
solves your iterate through all files; andsome_specific_yamltool
should probably beyq
, which is meant for exactly this kind of operation.