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:
declare -a args=("$@")
- Name the array however you like--it doesn't have to be called
args
.
- You can even omit
declare -a
, if you want.
- It doesn't really need to appear near the beginning. It just has to run before any of the code that accesses
args
runs. It could even appear below the definitions of shell functions that use args
. 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 of args
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, after args=("$@")
, $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:
shift
You would write:
args=("${args[@]:1}")
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:
ek@Cord:~$ args=('foo bar' 'baz quux' 'ham spam')
ek@Cord:~$ printf '[%s]\n' "${args[@]}"
[foo bar]
[baz quux]
[ham spam]
ek@Cord:~$ printf '[%s]\n' "${args[@]:1}"
[baz quux]
[ham spam]
The code corresponding to
if [ "$1" = 2 ] || [ "$1" = 3 ]; then
version="$1"
shift
else
version=2
fi
would be:
if [ "${args[0]}" = 2 ] || [ "${args[0]}" = 3 ]; then
version="${args[0]}"
args=("${args[@]:1}")
else
version=2
fi
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 calling run
, outside of any shell function, and then calling run "$@"
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.
$@
is expanded to its value before parse is called, so it never sees$@
but only its contents. You'll have toshift
in your main function or pass${@:2}
(if you're using bash). If parse doesn't output anything yet, you could echo the remaining parameters and call parse likeparams=$(parse "$@")
. BTW this looks like a shell script, why the python tag?shift
in main means I don't use the parse function (which I like) and passing the result of parse to run didn't work for me, as the original code uses it in a weird way... (the Py tag was a mistake)parse
function, and perform the actual shift in the main body (i.e.parse "$@"; shift $?
)