August 15th, 2024

Metaprogramming in Bash

Adam Young's blog post on metaprogramming in Bash emphasizes efficient scripting for managing multiple machines, advocating for reusable functions, dynamic variable assignment, and potential Ansible integration for improved automation.

Read original articleLink Icon
Metaprogramming in Bash

The blog post by Adam Young discusses metaprogramming in Bash, focusing on managing operations across multiple machines in different roles, such as build, test, and QA servers. Young outlines the need for efficient scripting to handle commands like power cycling machines and connecting via SSH. He emphasizes the importance of refactoring repetitive code into reusable functions to minimize errors and improve maintainability. The post details how to create a generic SSH function and an IPMI command function, allowing for easier management of multiple machines. Young also highlights the shortcomings of using parameter lists and introduces the use of `eval` and `awk` for dynamic variable assignment based on server configurations. He notes that this approach aids in debugging and helps users understand the commands being executed. The author expresses a desire for further improvements, such as integrating with Ansible for automation and enhancing the script's efficiency. Overall, the post serves as a guide for developers looking to streamline their Bash scripting practices for system management.

- The author discusses metaprogramming techniques in Bash for managing multiple machines.

- Emphasis is placed on refactoring repetitive code into reusable functions to reduce errors.

- The use of `eval` and `awk` is introduced for dynamic variable assignment based on server configurations.

- The post highlights the importance of debugging and user understanding of executed commands.

- Future improvements include potential integration with Ansible for automation.

Link Icon 5 comments
By @JNRowe - 5 months
If you're a zsh user there is a fun^wquirky way to achieve similar functionality using the MULTI_FUNC_DEF¹ option. zsh allows you to define multiple functions at the same time, so you can parametrize functions with something like:

    build_ssh qa_ssh test_ssh() {
        echo ${(U)0%_*}_SYSTEMIP
    }
    build_ssh  # Produces BUILD_SYSTEMIP
    qa_ssh  # Produces QA_SYSTEMIP
(You'd need the 'P' flag to perform secondary expansion to get the value of *_SYSTEMIP if you were using this for the same reason as in the post).

You can even use brace expansion in your function definition, iff you define it using the function keyword like "function {build,qa,test}_ssh()".

---

There might be a nice solution to this with fish too, as you could create per-machine symlinks in your ~/.config/fish/functions to add a new machine.

¹ https://zsh.sourceforge.io/Doc/Release/Options.html

By @000ooo000 - 5 months
FYI one can avoid duping a command to echo it out as in some of the examples by using set -x (set -o xtrace). It can be disabled with set +x afterwards, or you can run it and your desired command in a subshell if performance is not a priority.

   (
       set -x
       ssh ...
   )
By @loa_in_ - 5 months
Having two copies of cmdline in the function is not a good design and probably most easily avoided with metaprogramming by building the cmdline first and then using it to echo and run it.

I do suspect the examples are intentionally very simplified though.

By @mcint - 5 months
I'm partial to defining echoeval()

  echoeval(){
    CMD="$@" 
    echo "$ $CMD"
    eval "$CMD"
  }
  echoeval ssh test -A
Variants that take a first parameter for coloring output, directing to stdout/stderr, or (for one-shot commands) variant that colors output based on return value -- are functions I have written.
By @Tyr42 - 5 months
I like it, but once you get that far it's probably better to have a tiny python script parse the config file and then use it.