clns
clns

Reputation: 2304

Change global positional parameters inside a Bash function

Is there a way to set the positional parameters of a bash script from within a function?

In the global scope one can use set -- <arguments> to change the positional arguments, but it doesn't work inside a function because it changes the function's positional parameters.

A quick illustration:

# file name: script.sh
# called as: ./script.sh -opt1 -opt2 arg1 arg2

function change_args() {
    set -- "a" "c" # this doesn't change the global args
}

echo "original args: $@" # original args: -opt1 -opt2 arg1 arg2
change_args
echo "changed args: $@" # changed args: -opt1 -opt2 arg1 arg2
# desired outcome:        changed args: a c

Upvotes: 11

Views: 3109

Answers (3)

firmament
firmament

Reputation: 130

I was searching for this myself and came up with the below, i.e. return a space separated string of the array (sorry, positional parameters) from the function by printing it to stdout and capture it with command substitution, then parse the string word for word and append it back into the positional arguments. Clear as mud!

Obviously won't work for args with spaces in them below but that's of course possible to workaround too.

#!/bin/sh  
fn() {
    set -- d e f
    res=""
    for i; do
        res="${res:+${res} }${i}"
    done
    printf "%s\n" "$res"
}
set -- a b c
rv=$(fn)
set --
for word in $rv; do
    set -- "$@" "$word"
done
for i; do
    echo positional parms "$i"
done

Upvotes: 0

Ciro Costa
Ciro Costa

Reputation: 2585

As stated before, the answer is no, but if someone need this there's the option of setting an external array (_ARRAY), modifying it from within the function and then using set -- ${_ARRAY[@]} after the fact. For example:

#!/bin/bash

_ARGS=()

shift_globally () {
  shift
  _ARGS=$@
}

echo "before: " "$@"
shift_globally "$@"
set -- "${_ARGS[@]}"
echo "after: " "$@"

If you test it:

./test.sh a b c d
> before:  a b c d
> after:  b c d

It's not technically what you're asking for but it's a workaround that might help someone who needs a similar behaviour.

Upvotes: 5

kojiro
kojiro

Reputation: 77127

Not really. A function actually has its own scope. A parameter assigned in a function is global by default, but if you explicitly declare it it has local scope:

foo() {
    x=3 # global
    declare y # local
    ...
}

The positional parameters are always local to the function, so anything you do to manipulate them will only take effect within the function scope.

Technically, you can always use recursion to solve this issue. If you can confidently call the original script again, you can reorder the arguments that way. E.g.

declare -i depth

outer() {
    if (( depth++ < 1 )); then
        outer "${@:4:$#}" "${@:1:3}"
        return
    fi
    main "$@"
}

main() {
    printf '%s\n' "$@"
}

outer "$@"

This seems like an error prone and confusing solution to a problem I don't understand, but it essentially works.

Upvotes: 1

Related Questions