For some one-liner transformations, I like to use pyp (distributed as "pypyp").
Unlabeled lines
If your lines are not labelled, you can do an unconditional transformation like so (%3: 0->2 1->0 2->1):
$ seq 9 | pyp '[elem for group in zip(lines[1::3],lines[2::3],lines[0::3]) for elem in group]'
Shorter example output looks like this:
$ seq 6 | pyp '[elem for group in zip(lines[0::3],lines[2::3],lines[1::3]) for elem in group]' 1 3 2 4 6 5
Labeled lines
Data setup:
$ pyp -b 'faker.Faker.seed(202418);f=faker.Faker()' \ 'json.dumps( [dict(name=f.first_name(), address=f.address().replace("\n",", "), phone=f.phone_number()) for i in range(2)])' \ | jq '.[]|to_entries[]|(.key + ": " + .value)' -r \ | tee q202418.data
Parsing by prefix, not as robust as parsing into records:
$ <q202418.data \ pyp -b 'f=lambda categ: filter(lambda l: l.startswith(categ), lines)' \ 'names=f("name:"); addrs=f("address:"); ps=f("phone:"); [line for group in zip(addrs, ps, names) for line in group]'
For a discussion of more robust multi-line record parsing, see https://stackoverflow.com/questions/13295083/parsing-a-multi-line-data-file-with-python.
Other pyp notes
pyp's --explain flag can help show what's happening, or allow you to save it as a standalone script:
$ seq 9 | pyp --explain '[elem for group in zip(lines[1::3],lines[2::3],lines[0::3]) for elem in group]' #!/usr/bin/env python3 import sys from pyp import pypprint lines = [x.rstrip('\n') for x in sys.stdin] output = [elem for group in zip(lines[1::3], lines[2::3], lines[0::3]) for elem in group] if output is not None: pypprint(output)
Pyp supports customization through a PYP_CONFIG_FILE.
pyp performs static analysis of the input code, some AST transformations and pruning, then compiles and executes the result, or with --explain, unparses the AST back to source. It imports packages from its environment, and errors early on unknown or undefined symbols.
See other usage examples at https://pypi.org/project/pypyp/.