4

Using $@ you can do things to a list of files in bash. Example:

script.sh:

#!/bin/bash
list=$@
for file in $list; do _commands_; done

Then i can call this program with

~/path/to/./script dir1/{subdir1/*.dat,subdir2/*}

This argument will expand to a number of arguments that becomes $list. But now i want other arguments, say $1, $2, and this listing to be $3. So i want the expansion of dir1/{subdir1/*.dat,subdir2/*} to occur inside the script, instead of becoming many arguments. On the command line you can do this:

find dir1/{subdir1/*.dat,subdir2/*}

And get the desired output, i.e. a list if files. So I tried things like this:

arg1=$1
arg2=$2
list=$(find $3)
for file in $list; do _commands_; done
...

calling:

~/path/to/./script arg_1 arg_2 'dir1/{subdir1/*.dat,subdir2/*}'

But without success. Some help on how to make this list expand into a variable inside the script would be well appreciated!:)

EDIT: So answers below gave the solution using these commands:

arg1="$1"
arg2="$2"
shift 2

for f in "$@"; do echo "processing $f"; done;

But out of curiosity, is it still possible to pass the string dir1/{subdir1/*.dat,subdir2/*} to a find command (or whichever means to the same end) inside the script, without using $@, and obtain the list that way? This could be useful e.g. if it is preferable to have the listing as not the first or last argument, or maybe in some other cases, even if it requires escaping characters or quoting the argument.

Jonatan Öström
  • 2,428
  • 1
  • 16
  • 27
  • 3
    `list=$@` doesn't copy `$@` into an array. `list=( "$@" )` would, though you'd need to expand it as `for file in "${list[@]}"`. Doing it the other way will have subtle (or not-so-subtle) bugs when your inputs get interesting (glob characters, spaces, etc). – Charles Duffy Feb 02 '15 at 15:02
  • The listing is going to be of data files with fairly nice names so that shouldn't be an issue. Though $list is used in a call for gnuplot to plot the listed files in a single plot. with `plot for [file in '$list'] file ...` And the format it gets now is working well. – Jonatan Öström Feb 02 '15 at 16:01
  • Sounds like this works for your needs now, then. I'd urge you to be careful of making a habit of it, though -- I've seen TB of backups deleted because someone assumed a directory would only have nice names, then a buffer overflow created a file with an asterisk surrounded by spaces. – Charles Duffy Feb 02 '15 at 16:08

2 Answers2

4

You can have this code in your script:

arg1="$1"
arg2="$2"
shift 2

for f in "$@"; do echo "processing $f"; done;

Then call it as:

~/path/to/script arg_1 arg_2 dir1/{subdir1/*.dat,subdir2/*}

Using shift 2 will move positional parameters 2 places thus making $3 as $1 and $4 as $2 etc. You can then directly invoke $@ to iterate the rest of the arguments.

As per help shift:

shift: shift [n]

  Shift positional parameters.

  Rename the positional parameters $N+1,$N+2 ... to $1,$2 ...  If N is
anubhava
  • 761,203
  • 64
  • 569
  • 643
  • 1
    This seems perfect, and no need for escaping characters or quoting in the command line arguments. Wasn't familiar with the shift command. Thanks a lot! – Jonatan Öström Feb 02 '15 at 16:03
0

The shell expansion is performed by the shell, before your script is even called. That means you'll have to quote/escape the parameters. In the script, you can use eval to perform the expansion.

#!/bin/bash
arg1="$1" ; shift
arg2="$2" ; shift
eval "list=($@)"
for q in "${list[@]}" ; do echo "$q" ; done

$ ./a 123 456 'a{b,c}' 'd*'
ab ac d.pl docs

I don't see the point of doing the expansion inside the script in your example.

#!/bin/bash
arg1="$1" ; shift
arg2="$2" ; shift
list=("$@")
for q in "${list[@]}" ; do echo "$q" ; done

or just

#!/bin/bash
arg1="$1" ; shift
arg2="$2" ; shift
for q in "$@" ; do echo "$q" ; done

$ ./a 123 456 a{b,c} d*
ab
ac
d.pl
docs
ikegami
  • 367,544
  • 15
  • 269
  • 518
  • 1
    What if the arguments contain a semi-colon. Using eval to parse command line arguments is dangerous and you have given no indication of this. –  Feb 02 '15 at 15:48
  • Yes, you are right. No need to expand inside the script. I falsely assumed that that would have to be the case:) Thanks a lot! Checked the first answer though;) – Jonatan Öström Feb 02 '15 at 16:06
  • I wasn't mistaken(although i thought i was due to the shifts).Okay so pass this as the third arg as `"1);ls;x=(1"` and see what happens –  Feb 02 '15 at 16:11
  • 1
    It isn't because they are quoted. Pass `"1);ls;x=(1"` to anubhava's script as third arg and to yours as third arg and see the difference –  Feb 02 '15 at 16:13
  • 1
    All i wanted was you to warn it could happen in your answer so someone doesn't potentially(at worst) destroy their system by accident. –  Feb 02 '15 at 16:16
  • @JID, You're really missing the point. The OP asked how to have `script2 '$( echo foo >&2 )'` work the same as `script1 $( echo foo >&2 )`. Sure, `bash` commands can destroy one's system. What makes you think that needs to be mentioned. – ikegami Feb 02 '15 at 16:30