-1

I am trying to pass all parameters from one script to another. However, there is an issue when the other script is sourced; all parameters are not passing correctly.

first.sh:

#!/usr/bin/bash


while getopts a option 
do 
    case "${option}"
    in
    a) echo 'OptionA:somevalue';; 
    esac 
done

# This way works
./second.sh "$@"

# Does not work when source command is used
#source ./second.sh "$@"

second.sh:

#!/usr/bin/bash


while getopts b:c option 
do 
    case "${option}"
    in
    b) echo 'OptionB:'"${OPTARG}";; 
    c) echo 'OptionC:somevalue';;
    esac 
done

Output:

$ ./test.sh -a -b foo -c
OptionA:somevalue
./first.sh: illegal option -- b
./second.sh: illegal option -- a
OptionB:foo
OptionC:somevalue

Expected output:

$ ./test.sh -a -b foo -c
OptionA:somevalue
OptionB:foo
OptionC:somevalue

What to achieve?

Passing the parameters correctly to second.sh with source command, getting rid of illegal option and compatibility with other shells.

Edit: Updated the question again with more clear examples.

0

1 Answer 1

4

Consider how a while getopts loop works even in the normal case. getopts is called for each iteration of the loop, and even though it doesn't modify the command line args (positional parameters) themselves, it somehow needs to know where in the list of args to look at next. It has to do this by keeping "hidden" state, something not explicitly passed in the call.

This is described in Bash's reference manual:

Each time it is invoked, getopts places the next option in the shell variable name, initializing name if it does not exist, and the index of the next argument to be processed into the variable OPTIND. OPTIND is initialized to 1 each time the shell or a shell script is invoked. When an option requires an argument, getopts places that argument into the variable OPTARG. The shell does not reset OPTIND automatically; it must be manually reset between multiple calls to getopts within the same shell invocation if a new set of parameters is to be used.

When you source a script, it runs in the same shell environment as the main script, and this includes OPTIND.

As hinted in the manual, just set OPTIND=1 before starting a new getopts loop. (Don't try to unset it, it may cause issues in some shells.)


As an example, this:

set -- -a foo
while getopts a:b: option; do
    echo "$option: $OPTARG"
done
set -- -b first -b second
# OPTIND=1
while getopts a:b: option; do
    echo "$option: $OPTARG"
done

prints

a: foo
b: second

since the second loop continues from the position the first left off, missing the -b first.

Uncomment the OPTIND=1 assignment in the middle, and you get the expected output:

a: foo
b: first
b: second

As for storing the arguments separately from $@, doing args+="-a ${OPTARG} " builds a single string, while the set of command line arguments is actually a list/array of distinct strings. The difference is most evident when any of the arguments themselves contain whitespace. The two sets of args (-a, foo bar) and (-a foo, bar) both join into -a foo bar, and there's no way to tell the difference from the latter string.

Instead, use an array to store the args as distinct strings.

while getopts a:b:d option 
do 
    case "${option}"
    in
    a) args+=(-a "$OPTARG");; 
    b) args+=(-b "$OPTARG");; 
    d) echo 'Option:D';; 
    esac 
done
# ...
./args.sh "${args[@]}"

(See How can we run a command stored in a variable? for further examples etc.)


Note that, the standard . (dot) command for sourcing a script does not take arguments, but instead the sourced script sees the same $@ as the main script. (And changes made to $@ in the sourced script are visible in the main script.)

While Bash's ./source supports passing a new set of args, if you call source filename without a set of args, the sourced script does not get an empty list, but the args of the main script.

So, if you do this:

source ./args.sh "${args[@]}"

you may want to take care to check that args is not empty. Or just reset the args of main script before sourcing the other one. Of course, that will trash the original set of args, but if you need them, you could save them to another array first:

orig_args=( "$@" )   # if needed 
set -- "${args[@]}"
source ./args.sh
17
  • Well written and explained. I wanted to write a comment in a similar line, but would have been stuck with, multiple getopts in the same shell will ruin the functionality and can lead to a breakage if not used with care. Thanks for all the details and an actual explanation, so I can actually understand the behaviour I just found out ;) Commented Mar 9, 2024 at 17:04
  • Your answer does't provides a solution for my question. I need to achieve this when other script are begin sourced. Commented Mar 9, 2024 at 17:37
  • @Zero, did you try what I suggested? Commented Mar 9, 2024 at 18:01
  • @Zero, note that your post says "all parameters are not passing correctly", but you haven't shown what "$@" (or $1, $2...) hold in the inner script. Commented Mar 9, 2024 at 18:08
  • 1
    @Zero, well, I didn't give a complete code, to be honest. I tried to explain the issue and said "As hinted in the manual, just set OPTIND=1 before starting a new getopts loop." It works for me that way. You didn't say if you tried resetting OPTIND that way in your code. Commented Mar 10, 2024 at 8:49

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.