16

Suppose I have a graphical program named app. Usage example: app -t 'first tab' -t 'second tab' opens two 'tabs' in that program.

The question is: how can I execute the command (i.e. app) from within a bash script if the number of arguments can vary?

Consider this:

#!/bin/bash
tabs=(
    'first tab'
    'second tab'
)

# Open the app (starting with some tabs).
app  # ... How to get `app -t 'first tab' -t 'second tab'`?

I would like the above script to have an effect equivalent to app -t 'first tab' -t 'second tab'. How can such a bash script be written?

Edit: note that the question is asking about composing command line arguments on the fly using an array of arguments.

Kusalananda
  • 333,661
Flux
  • 2,938

4 Answers4

18

Giving the arguments from an array is easy, "${array[@]}" expands to the array entries as distinct words (arguments). We just need to add the -t flags. To do that, we can loop over the first array, and build another array for the full list of arguments, adding the -t flags as we go:

#!/bin/bash
tabs=("first tab" "second tab")
args=()
for t in "${tabs[@]}" ; do 
    args+=(-t "$t")
done
app "${args[@]}"

Use "$@" instead of "${tabs[@]}" to take the command line arguments of the script instead of a hard coded list.

Related: How can we run a command stored in a variable?

ilkkachu
  • 138,973
  • 1
    This won't quote args that have spaces. It's not the same. – ygoe Oct 24 '19 at 20:27
  • 2
    @ygoe, I'm not sure what you're comparing against. Quoting really only means anything when a shell command line is first processed. After that, it's better to think of shell "words" or the arguments that go to the command the shell runs (shell "words" can contain whitespace, so it's a somewhat confusing term). The array expansion like "${args[@]}" expands each element of the array to a separate word/argument, so the above works like app -t "first tab" -t "second tab". If there's some corner case where it fails, please do tell. – ilkkachu Oct 24 '19 at 20:54
6
tabs=("-t" "one tab" "-t" "second tab")
echo app "${tabs[@]}"
app -t one tab -t second tab

So now you should convert your original array to array with "-t" flags. Hope it's not a problem at all.

3

It's easier with zsh:

#!/bin/zsh -
tabs=(
    'first tab'
    'second tab'
)

app -t$^tabs

That calls app as if you had entered:

app -t'first tab' -t'second tab'

rc, es and fish do that implicitly (zsh's $^array is actually inspired from rc's ^):

In those shells,

app -t$tabs

would do that as well. In zsh, you can get that behaviour as well by default by turning the rcexpandparam option on with set -o rcexpandparam.

In ksh93, bash and zsh, you can also use the ${array[@]/pattern/replacement} operator from ksh93:

#! /bin/bash -
tabs=(
    'first tab'
    'second tab'
)

app "${tabs[@]/#/-t}"

Where we replace the (zero-width) start of each element with -t, so in effect prepending -t to each.

To call app as if with

app -t 'first tab' -t 'second tab'

as opposed to

app -t'first tab' -t'second tab'

That is where -t and first tab are two different arguments to app, with zsh:

app ${${:--t}:^^tabs}

using the ${array1:^^array2} array-zipping operator where ${:--t} (minimal form of ${var:-default}) is used to inline the first array.

  • Downvoted because the question is explicitely about Bash, so "it’s easier with [something that’s not Bash]" is not a valid answer. – bfontaine Feb 22 '24 at 18:35
  • 1
    @bfontaine, I've now added an approach which also works in bash for those people coming here and are constrained to using bash. In any case, note that answers here are not intended only for the OP, but anyone with similar requirement and not everyone is stuck with bash, you'll find that giving alternatives is generally encouraged rather than frowned upon. – Stéphane Chazelas Feb 23 '24 at 07:53
-3

Basically the following bash script will read the file which include sites line by line and open each site in a new tab.

#!/bin/bash
while read line
do 
    xdg-open "$line"
done < /root/file

Usage

./script.sh sites

Chris Davies
  • 116,213
  • 16
  • 160
  • 287
  • 1
    Ah but you are running the command (xdg-open in your case) multiple times. I would like the command to be run only once. – Flux Dec 23 '17 at 08:44
  • 1
    The question is about getting the arguments from an array, not a file. – Barmar Dec 23 '17 at 11:43