1

I have a bash shell script(my_script.sh) which runs a script(their_script) during execution. My script (my_script.sh) looks like this :

THISDIR=`dirname $(readlink -f $0)`
main() {
    cd $THISDIR
    source their-script
}
main "$@"

their-script is a file I should not change. their-script has this stuff:

BDIR="$1"
...
BDIR=`readlink -f "$BDIR"

Everything above works great. I would like to add some options, so I changed my_script.sh to look like this:

THISDIR=`dirname $(readlink -f $0)`
check_options() {
    while [ "$1" != "" ]; do
        case $1 in
            --username )  shift
                          OPTIONS_USERNAME=$1
                          ;;
            * )           # No more options
                          ;;
        esac
        shift
    done
}

main() {
    check_options
    cd $THISDIR
    source their-script
}
main "$@"

Now, when I run ./my_script.sh --username example, their-script fails during execution of:

readlink -f --username

and spits out this line:

readlink: unrecognized option '--username'

How can I prevent my script's positional parameters from breaking other scripts in this way?

2

2 Answers 2

4

As @roaima pointed out, you likely want to run the script in a separate process. Sourcing it in your code without full understanding of it's internal workings can be rather dangerous:

FILE_TO_REMOVE="/tmp/foobar"
source some-cool-script
rm -Rf "$FILE_TO_REMOVE"

is rather obviously not the right way to go about things , because the cool script may so happen to include

FILE_TO_REMOVE=/

Not to mention that the script might be changed at any time (even if it is not "actively" maintained).

Apart from that, there are several things you should consider:

  1. processing positional parameters is better controlled by the number of parameters left:

    while [ $# -gt 0 ]; then
    ...
    done
    

    empty string like "" may be a perfectly valid argument, unless you want to use it as a separator. Even if you do (traditionally -- is used), it is arguably better to keep the above condition and use:

        case "$1" in
            ...
            "")
                shift
                break
                ;;
            ...
        esac
    

    which is easier to maintain.

  2. The $@ variable is per scope. That means that if you decide to execute the external script as it is usually done, i.e. by calling it instead of sourcing it in your code, you'll need to pass it the arguments:

    their-script "$@"
    

    Using a full path to the script might not be a bad idea either, especially if the script is being run with elevated privileges.

  3. The above also means, that this actually doesn't check command line arguments:

    main() {
        check_options
    }
    main "$@"
    

    You need to call it as

        check_options "$@"
    
  4. The above however also means that arguments in main will not be modified in any way by check_options. If you need to filter some options so that they don't end up crashing the external script (and given your question you do) it leaves you with two options:

    • put the option parsing into main or even into the global scope of your script. It's faster and a bit messy as far as code cleanliness is considered.

    • keep option parsing in a separate function and do some variable juggling:

      check_options {
          # parse options magic
          # what needs to be passed over is
          # kept in a separate variable
          PASS_THROUGH_OPTS=...
      }
      
      main {
          check_options "$@"
          set -- $PASS_THROUGH_OPTS
          ...
      }
      

      Note that this doesn't handle the possibility that positional parameters may contain spaces or other word separators. I don;t know of any other way to handle this properly than by using arrays (which is implementation specific) and involves things like:

      check_options {
          ...
              # parameter should be kept for further use
              x=( "${x[@]}" "$1" )
          ...
      }
      
      check_options "$@"
      set -- "${x[@]}"
      
3

The source their-script command tells your shell to execute their-script directly in the context of your script. This means it has access to all your variables, and can even change them.

If you remove the word source and just run their-script as a command it will be unable to affect any of your code, and its $1 will be the first argument you give it, so in this example its $1 will take the value banana:

their-script banana

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .