2

I have the following bash script code to open multiple session of a python script with different input argument in different terminal tabs.

#!/bin/bash

cd /home/me/experiment

tab=" --tab-with-profile=Default"
options=()  #(--tab --title=Terminal)

cmds[1]="./new_path.py -s 593000 -f 593200"
titles[1]="0593000_0593200"

cmds[2]="./new_path.py -s 593200 -f 593400"
titles[2]="0593200_0593400"

cmds[3]="./new_path.py -s 593400 -f 593600"
titles[3]="0593400_0593600"

cmds[4]="./new_path.py -s 593600 -f 593800"
titles[4]="0593600_0593800"

cmds[5]="./new_path.py -s 593800 -f 594000"
titles[5]="0593800_0594000"



for i in 1 2 3 4 5; do
  options+=($tab --title="${titles[i]}"  -e "bash -c \"${cmds[i]} ; bash\"" )          
done

gnome-terminal "${options[@]}"


exit 0

How can I avoid typing the same command multiple times using for loop and bash script variable?

  • If this is something you need to do regularly, it might be worth your while to have a look at tmux or gnu screen. – evilsoup Sep 7 '14 at 16:37
4

You can shorten your script to:

#!/bin/bash

cd /home/me/experiment

args=()
for ((step=200, s=593000; (f=s+step)<=594000; s+=step)); do
    cmd="./new_path.py -s $s -f $f";  printf -v title '%07d_%07d' $s $f
    args+=(--tab-with-profile=Default -t $title -e "sh -c \"$cmd; bash\"")
done

gnome-terminal "${args[@]}"

To eliminate repetition, I got rid of the cmds and titles arrays altogether and moved construction of those strings into the loop, where it could be automated.

I did test this of course; though in my test I used mate-terminal instead of gnome-terminal. They are very similar, mate-terminal being a fork of gnome-terminal, and I'd be quite surprised if any problems arose from that difference; mate-terminal's features are a subset of those provided by gnome-terminal. Most importantly, both accept the same --tab-with-profile, -t, and -e options.

A more detailed explanation of how the above code works, and why chose that implementation, follows, in case you're interested.


Instead of looping over the sequence 1 2 3 4 5, which is meaningless to the problem, I made the loop variable s, the value passed to ./new_path.py after -s. I also defined f and ensured it took on a value 200 greater than s in each iteration of the loop; this is the value passed to ./new_path.py after -f.

That made it easy to construct each iteration's cmd and title strings. (cmd is the value passed to gnome-terminal after -e, and title is the value passed after -t.)

  • Making cmd was particularly simple. s and f could just appear in the string without modification, dereferenced as $s and $f within the double-quoted expression.
  • For title I noticed the leading zeros in your manually written strings (e.g., "0593000_0593200"). I inferred you probably want the title numbers zero-padded whenever they're fewer than seven digits, and used bash's printf builtin to achieve this.

There were several possible constructions to choose from, in writing the loop. In addition to the more common for variable in list syntax, bash supports C-style for with (( initialization; condition; increment-or-other-action)) syntax, which I decided to use.

My goals in deciding how to write the loop included:

  • making it so that when the loop is modified later, each number can be changed in just one place (e.g., avoiding having 200 appear multiple times in the code), and
  • reflecting the logic of the underlying problem being solved.

In bash, sequences of numbers are usually best generated with {start..end..step} (or just {start..end} for contiguous sequences). But brace expansion is performed before shell parameter expansion, so start, end, and step cannot be variables (like $step).

A for s in seq 593000 593800 $step loop (and setting f=$((s+step)) inside the loop) would've worked for this, and I would probably have done it that way if easy code maintenance were my only concern.

Based on your original script, it seems the underlying problem being solved is, or is something like, this:

  • Perform runs (of some experiment or calculation).
  • Vary some important parameter, whose starting and final values are specified to ./new_path.py via -s and -f.
  • The important parameter ranges from 593000 to 594000.
  • Vary sequentially in chunks of 200. Cover the range by running five such tasks in parallel.

This suggested it would be valuable to write the loop in such a way that the lowest -s value and highest -f value were specified (rather than the lowest and highest -s values). I used a C-style for ((...)) loop, which facilitated that in a reasonably concise and readable way.

Note that this was not the only way to do it, nor is there any one "Right Way." In particular, I could've instead defined step and max (and initialized s to its minimum value) before the loop, then used a while loop with arithmetic comparison (via (( with <=, or [[ with -le) in the condition.

I made a few other changes, compared to your original script, by they are minor and entirely a matter of stylistic preference.

  • I renamed options to args because "options" is often used specifically to mean flags (such as those starting with - and --) rather than all command-line arguments.

  • Since the += operator is being used to build an array of arguments rather than the final command string itself, no leading or trailing whitespace is needed in its elements. So I removed the leading space before --tab-with-profile.

  • In the flags passed to gnome-terminal, I changed --title to -t for consistency with the choice of -e. (-t is the short form of --title=; -e is the short form of --command=.) This also made it a little easier to read because it let $title be a separate array element instead of having to be combined into a string as "--title=$title".

  • I used sh -c ... instead of bash -c ... as the command passed to gnome-terminal, since the generic sh (which in Ubuntu is the simpler, lighter weight dash) is sufficient to the task of chaining two simple commands with ;. (I didn't change the bash after ; to sh, because the interactive shell that runs in each tab after ./new_path.py finishes should of course still be bash.)

    In this context, this is definitely not a performance consideration, but just a matter of style. I am not suggesting that using sh -c instead of bash -c for this is the right way or that it is objectively preferable.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.