6

I wants to know how can I reference the $@ from another function or another script file without pass it just like what getopts do. Thank you very much!

# previous question : How bash getopts get to know what arguments the call has

A() {
  B 
} 

B() {

  # how to reference the $@ of A here ?
  # which will be 1 2 3 4


}

A 1 2 3 4
4

3 Answers 3

14

You can't without A making its positional parameters available to B one way or another. Could be by passing them along:

A() {
  B "$@"
}
B() {
  [ "$#" -eq 0 ] || printf '<%s>\n' "$@"
}

Or storing them in an array:

A() {
  A_args=( "$@" )
  B
}
B() {
  [ "${#A_args[@]}" -eq 0 ] || printf '<%s>\n' "${A_args[@]}"
}

As bash, like ash/ksh88/zsh (but unlike ksh93 or zsh's private variables) does dynamic scoping for its local variables, you could even make that A_args variable local to A and it would still be available in the functions that A calls.

bash lets you access variables of its parent scope even when they're shadowed by local variables in the current scope by exploiting a current bug of its unset builtin (which under some conditions doesn't unset but reveal the variable from the parent scope), but you can't access the positional parameters from the parent scope that way because in bash (contrary to zsh/rc/fish...), positional parameters cannot be mapped to array variables as bash array design is shaped after that of the Korn shell where arrays are not really arrays.

If you want some code to be able to access or modify (via shift or set) the function's positional parameters or local options, or return from the function, you need that code to run in the scope of that function, so that cannot be in a separate function.

You could use aliases (which are the shell equivalent of C pre-processor macros, except they can't take arguments) or eval though.

Beware though that:

  • in the bash shell and when not in POSIX mode, alias expansion is disabled by default so you'll need shopt -s expand_aliases.
  • the alias must exists before the code of the function is read, and it is expanded inside the code of the function.

So you could do:

[ -z "$BASH_VERSION" ] || shopt -s expand_aliases # work around for bash
alias B='{
  [ "$#" -eq 0 || printf "<%s>\n" "$@"
  shift
}'
A() {
  echo "$#"
  B
  echo "$#"
}
A a b c

Or:

B='
  [ "$#" -eq 0 || printf "<%s>\n" "$@"
  shift
'
A() {
  echo "$#"
  eval "$B"
  echo "$#"
}
A a b c
9
  • can we posible accomplish something like what the native function getopts do? without pass arguments?
    – Till
    Commented Nov 21, 2023 at 9:18
  • getopts works on the positional parameters from the scope it's called in when not passed any extra arguments, it can't access positional parameters from a parent scope either. Commented Nov 21, 2023 at 9:29
  • can you explain this a little bit more? What do you mean by "getopts works on the positional parameters from the scope it's called", can we write a function to do the same? can you give me an example of this?
    – Till
    Commented Nov 21, 2023 at 9:50
  • What that means is just that if getopts is called in a function, it accesses the positional parameters of the function ($@ expands to the list of positional parameters), that's it. Even zsh which in the latest development version lets you do nameref var=argv cannot access positional parameters from a different function scope. Commented Nov 21, 2023 at 10:07
  • 1
    @Till getopts is not a function, it's a shell builtin.
    – choroba
    Commented Nov 21, 2023 at 10:28
5

Actually, in bash, when the extdebug option is enabled (normally used for the debugger), the list of positional parameters of all the functions in the call stack is made available in the $BASH_ARGV special array (though in reverse order) with $BASH_ARGC containing the number of arguments at each level.

Beware though that:

  • those are readonly (well, trying to change their value has no effect)
  • the extdebug must be set from the start (before the first function is invoked), but not on the bash command line (bash -O extdebug) as that invokes the debugger instead.
  • check the manual for the other implications of enabling that option.
shopt -s extdebug
shopt -u xpg_echo
A() {
  B x y
}

reverse_helper() { caller_args=( "${BASH_ARGV[@]:0:BASH_ARGC}" ); }

B() {
  local -a caller_args
  local IFS=,
  reverse_helper "${BASH_ARGV[@]:BASH_ARGC[0]:BASH_ARGC[1]}"
  echo "my args: $*"
  echo "my caller args: ${caller_args[*]}"
}

A a b c

Which gives:

my args: x,y
my caller args: a,b,c
0

Based on the comments, OP is asking about how to imitate the behaviors of builtins such as shift and getopts in a function. It's actually possible with some tricks, though I'm not sure it's a good idea to use it in production.

A() {
  B
} 

B() {
  trap 'shift;trap debug' debug
}

A 1 2 3 4

trap must be the last command in the function B.

If you want to preserve the original debug trap after return:

A() {
  B
} 

B() {
  local olddebug="$(trap -p debug)"
  trap "shift;trap debug;$olddebug" debug
}

A 1 2 3 4

Compared to aliases, functions could be referenced in another function before defined, called recursively, and redefined and get the new version called in the same function. Aliases would fail if you wrap everything in a script file including the aliases into a big function and call the function, though it's also rarely used in production.

1
  • You'll need to have a command after B in the A function for that debug trap to be run there. That command should probably not be a subshell. Commented Nov 22, 2023 at 9:31

You must log in to answer this question.

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