Search code examples
bashcode-generation

How to generate bash command from array and command template?


Now I use this bash command:

$ yarn b types && yarn w types && yarn g types && yarn s types

Is it possible to generate command like this in bash? (pseudo code):

$ exec ['b', 'w', 'g', 's'].map(input => `yarn ${input} types`).join(" && ")

If it is possible, which syntax will be here?

I am going to use this script in my package.json file (node). yarn workspaces foreach not suitable here, because of its poor output


Solution

  • Good-Practice Alternative: Use A Loop

    Code generation has serious security implications and is generally only for experts. Moreover, in present circumstances, you don't need it: a loop will suffice.

    buildAll() {
      for input; do
        yarn "$input" type || return
      done
    }
    buildAll b w g s
    

    ...has identical behavior, exiting early with a nonzero status if any of yarn b type, yarn w type, yarn g type or yarn s type fails, and exiting with a successful/zero status if all four succeed.

    As a one-liner, this would be:

    buildAll() { for i; do yarn "$i" type || return; done; }; buildAll b w g s
    

    This doesn't change in any substantial way if your items are in an array; if you have:

    types=( b w g s )
    

    ...then, just replace buildAll b w g s with buildAll "${types[@]}"


    What You Asked For: Performing Code Generation

    Before doing anything you saw here in your own code, review BashFAQ #48 regarding the security issues associated with eval.

    The ${var@Q} expansion requires bash 5.0 or newer; for older versions of bash, printf %q is the alternate way to escape variables' contents to be safe to parse as code. Note that this isn't any shorter or more readable than the alternatives above, and still involves a loop!

    : is used as a synonym to true; it lets us make all iterations of our loop identical, unconditionally prepending a &&.

    types=( b w g s )
    statement=':'
    
    for type in "${types[@]}"; do
      statement+=" && yarn ${type@Q} types"
    done
    eval "$statement"
    

    The version with support for older versions of bash is:

    types=( b w g s )
    statement=':'
    
    for type in "${types[@]}"; do
      printf -v type_q '%q' "$type"
      statement+=" && yarn $type_q types"
    done
    eval "$statement"