bash - Passing multiple sets of arguments to a command

12
2014-01
  • Alec

    instances contains several whitespace separated strings, as does snapshots. I want to run the command below, with each instance-snapshot pair.

    ec2-attach-volume --instance $instances --device /dev/sdf $snapshots
    

    For example, if instances contains A B C, and snapshots contains 1 2 3, I want the command to be called like so:

    ec2-attach-volume -C cert.pem -K pk.pem --instance A --device /dev/sdf 1
    ec2-attach-volume -C cert.pem -K pk.pem --instance B --device /dev/sdf 2
    ec2-attach-volume -C cert.pem -K pk.pem --instance C --device /dev/sdf 3
    

    I can do either one or the other with xargs -n 1, but how do I do both?

  • Answers
  • grawity

    Using the bash shell, one possible method is:

    instances=( `cat instances` )
    snapshots=( `cat snapshots` )
    
    for i in ${!instances[@]}; do
        ec2-attach-volume -C cert.pem -K pk.pem \
            --instance ${instances[i]} --snapshot ${snapshots[i]}
    done
    

    Here ${!instances[@]} expands to all indexes of the instances array (0, 1, 2, and so on). You can examine the array contents by doing declare -p instances snapshots after the first two lines.

  • Scott

    Here’s an approach that includes (some) error handling:

    xargs –n 1 < instances > instances.1_per_line    # “xargs –n 1” is equivalent to
    xargs –n 1 < snapshots > snapshots.1_per_line    # “xargs –n 1 echo”.
    if [ $(wc –l < instances.1_per_line)  !=  $(wc –l < snapshots.1_per_line) ]
    then
            echo "Different numbers of strings." >&2
    else
            paste instances.1_per_line snapshots.1_per_line | while read inst snap
            do
                    ec2-attach-volume -C cert.pem -K pk.pem --instance "$inst" \
                                    --device /dev/sdf "$snap"
            done
    fi
    rm instances.1_per_line snapshots.1_per_line
    

    This may work better than grawity’s answer for large files, as it doesn’t need to read the files into memory.  And this may work better in older versions of bash.  (If $(command) doesn’t work, try `command`.)  On the other hand, my answer will fail if any of the strings in the files contain quote characters.

  • Ole Tange

    To do it in parallel you can use GNU Parallel:

    parallel --xapply ec2-attach-volume --instance {1} --device /dev/sdf {2} ::: `cat instances` ::: `cat snapshots`
    

    Watch the intro videos to learn more: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1


  • Related Question

    How can I get a specific argument from a previous command in bash?
  • Wuffers

    In bash, you can use !* to get all the arguments from the previous command. As an example, if you did cp /some/path /some/other/path and then did mv !*, the second command would be expanded to mv /some/path /some/other/path.

    Is there anything like this that can be used to access a specific argument from a command instead of all of them?


  • Related Answers
  • Gilles

    In !*, ! is the history expansion prefix, and * is the word designator that means all arguments. You can memorize the general syntax as bang-line-colon-column (!line:column). There are many possible shortcuts: the default line is the previous line, the default column specifier is “all”, and you can leave off the colon if the column specifier is non-numeric (but !3 would mean line 3). You can use !:0 to refer to the command name, !:1, !:2, etc, to refer to successive arguments, !:$ or !:* for the last word, and more.

    See also this post by Michael Mrozek at Unix Stack Exchange.

  • Benoit

    Personnally, I really dislike this “expansion with exclamation mark” feature which will even disturb if you try echo "Hello World!" in interactive shells (so sourcing scripts that assume they will be run in non-interactive mode won't work at all).

    So, I set set +o histexpand and start recalling arguments with the following method:

    Esc, 1, Esc, Ctrl-Y => Insert first argument of previous command.
    

    note that the Esc-trick is because I don't have a meta key.