I have a script that gets N params. It first parses them (extracts a specific value X
), then calls another program with said value:
function main() {
parse "$@"
run "$@"
}
main "$@"
I want to add an optional param at the start of the args list (param to set the python version I'm running). So I added this to the parse functoin:
if [ "$1" = '2' ] || [ "$1" = '3' ]; then version=$1 && shift; else version=2; fi
However, after the parse ends, I'm stuck because the shift
doesn't affect the run function. How can I do this without checking again the value of the first value (which would defeat the whole purpose of having a parse function)
The scope of each shell function (as well as the scope of top-level code in the script) has its own positional parameters, and can only directly access its own. The cleanest solution is to place the values of the positional parameters you're interested in into an array. You can then read, and also modify, that array in multiple function scopes.
For example, you could have this code appear near the beginning of your script:
args
.declare -a
, if you want.args
runs. It could even appear below the definitions of shell functions that useargs
. For clarity, I suggest putting it somewhere near the beginning.Then you can operate on the
args
array from multiple functions. You don't have to pass it to the functions; they will already be able to access it. When code in a shell function modifies the contents ofargs
and then returns, code in the caller will be able to observe the changes.Positional parameters in Bourne-style shells, including Bash, use 1-based indexing. (This is because
$0
, which expands to the program name, is technically not a positional parameter, and does not change in function scopes.) But arrays in Bash use 0-based indexing. So, afterargs=("$@")
,$1
matches${args[0]}
,$2
matches${args[1]}
,$3
matches${args[2]}
, and so forth.$@
still matches${args[@]}
as you would expect.I wrote them that way, unquoted, for readability. You will, of course, almost always want to double-quote expansions involving your
args
array, just as you'd almost always want to double-quote expansions involving positional parameters.If you decide to go with this approach, then instead of:
You would write:
If you're new to arrays in Bash, you'll want to take a look at the relevant part of the Bash reference manual. You may also want to experiment interactively. For example:
The code corresponding to
would be:
Using an array is syntactically more cumbersome, but also more flexible.
On the other hand, since you seem to have most of the code of your script organized into a
run
function and its callees anyway, you might consider the alternative of parsing any special command-line arguments before callingrun
, outside of any shell function, and then callingrun "$@"
as you're already doing.There are programming languages whose associated cultures have a strong ethic of putting almost everything into small(ish), self-contained functions. Bash is not such a language, and the limited ways of returning complex data from a shell function is one of the reasons. You shouldn't be afraid to write shell functions, and you should even be willing to write a huge number of tiny shell functions. But I don't think you should be worried if it turns out that the best form for your script is something else.
For further reading, and some alternatives including a weird approach where you source the output of process substitution, see Gilles's answer to Function caller positional parameters.