A function cannot affect its caller's positional parameters. This is by design: positional parameters are meant to be private to the function.
Make your function work on an array.
myfunction () {
local _myfunction_arrayname=$1
shift
… # work on the positional parameters
eval "$_myfunction_arrayname=(\"\$@\")"
}
myfunction foo "$@"
set -- "${foo[@]}"
In ksh93 and bash, there's a roundabout way to do something approaching by combining an alias and the .
(source
) builtin with a process substitution. Example.
alias myfunction='. <(echo myfunction_body \"\$@\"; echo set -- "\"\${new_positional_parameters[@]}\"")'
Put the meat of the work of the function in myfunction_body
and make it set the array new_positional_parameters
. After a call to myfunction
, the positional parameters are set to the values that myfunction_body
puts in new_positional_parameters
.
SHELL SEQ:
Probably a useful means of bench-marking a shell's performance is to do a lot of very small, simple evaluations repetitively. It is important, I think, not just to loop, but to loop over input, because a shell needs to read <&0
.
I thought this would complement the tests @cuonglm already posted because it demonstrates a single shell process's performance once invoked, as opposed to his which demonstrates how quickly a shell process loads when invoked. In this way, between us, we cover both sides of the coin.
Here's a function to facilitate the demo:
sh_bench() ( #dont copy+paste comments
o=-c sh=$(command -v "$1") ; shift #get shell $PATH; toss $1
[ -z "${sh##*busybox}" ] && o='ash -c' #cause its weird
set -- "$sh" $o "'$(cat <&3)'" -- "$@" #$@ = invoke $shell
time env - "$sh" $o "while echo; do echo; done|$*" #time (env - sh|sh) AC/DC
) 3<<-\SCRIPT
#Everything from here down is run by the different shells
i="${2:-1}" l="${1:-100}" d="${3:-
}"; set -- "\$((n=\$n\${n:++\$i}))\$d" #prep loop; prep eval
set -- $1$1$1$1$1$1$1$1$1$1 #yup
while read m #iterate on input
do [ $(($i*50+${n:=-$i})) -gt "$(($l-$i))" ] || #eval ok?
eval echo -n \""$1$1$1$1$1"\" #yay!
[ $((n=$i+$n)) -gt "$(($l-$i))" ] && #end game?
echo "$n" && exit #and EXIT
echo -n "$n$d" #damn - maybe next time
done #done
#END
SCRIPT #end heredoc
It either increments a variable once per newline read or, as a slight-optimization, if it can, it increments 50 times per newline read. Every time the variable is incremented it is printed to stdout
. It behaves a lot like a sort of seq
cross nl
.
And just to make it very clear what it does - here's some truncated set -x;
output after inserting it just before time
in the function above:
time env - /usr/bin/busybox ash -c '
while echo; do echo; done |
/usr/bin/busybox ash -c '"'$(
cat <&3
)'"' -- 20 5 busybox'
So each shell is first called like:
env - $shell -c "while echo; do echo; done |..."
...to generate the input that it will need to loop over when it reads in 3<<\SCRIPT
- or when cat
does, anyway. And on the other side of that |pipe
it calls itself again like:
"...| $shell -c '$(cat <<\SCRIPT)' -- $args"
So aside from the initial call to env
(because cat
is actually called in the previous line); no other processes are invoked from the time it is called until it exits. At least, I hope that's true.
Before the numbers...
I should make some notes on portability.
posh
doesn't like $((n=n+1))
and insists on $((n=$n+1))
mksh
doesn't have a printf
builtin in most cases. Earlier tests had it lagging a great deal - it was invoking /usr/bin/printf
for every run. Hence the echo -n
above.
maybe more as I remember it...
Anyway, to the numbers:
for sh in dash busybox posh ksh mksh zsh bash
do sh_bench $sh 20 5 $sh 2>/dev/null
sh_bench $sh 500000 | wc -l
echo ; done
That'll get 'em all in one go...
0dash5dash10dash15dash20
real 0m0.909s
user 0m0.897s
sys 0m0.070s
500001
0busybox5busybox10busybox15busybox20
real 0m1.809s
user 0m1.787s
sys 0m0.107s
500001
0posh5posh10posh15posh20
real 0m2.010s
user 0m2.060s
sys 0m0.067s
500001
0ksh5ksh10ksh15ksh20
real 0m2.019s
user 0m1.970s
sys 0m0.047s
500001
0mksh5mksh10mksh15mksh20
real 0m2.287s
user 0m2.340s
sys 0m0.073s
500001
0zsh5zsh10zsh15zsh20
real 0m2.648s
user 0m2.223s
sys 0m0.423s
500001
0bash5bash10bash15bash20
real 0m3.966s
user 0m3.907s
sys 0m0.213s
500001
ARBITRARY = MAYBE OK?
Still, this is a rather arbitrary test, but it does test reading input, arithmetic evaluation, and variable expansion. Maybe not comprehensive, but possibly near to there.
EDIT by Teresa e Junior: @mikeserv and I have done many other tests (see our chat for details), and we found the results could be summarized like this:
- If you need speed, go definitely with dash, it is much faster than any other shell and about 4x faster than bash.
- While busybox's shell can be much slower than dash, in some tests it could be faster, because it has many of its own userland utilities, like
grep
, sed
, sort
, etc., which don't have as many features as the commonly used GNU utilities, but can get the work done as much.
- If speed is not everything you care about, ksh (or ksh93) can be considered the best compromisse between speed and features. It's speed compares to the smaller mksh, which is way faster than bash, and it has also some unique features, like floating point arithmetic.
- Although bash is famous for its simplicity, stability, and functionality, it was the slowest of all shells in the majority of our tests, and by a large margin.
Best Answer
Referring to 3.4.2 Special Parameters from Bash Reference Manual.
Special Parameters:
This can be also print from man page of bash:
The above are the same as the special parameters defined POSIX.
In addition, there are the positional parameters
$1
,$2
, ... that contain the command line arguments to the shell or the current function (3.4.1 Positional Parameters). They are also a POSIX feature.Older versions of Bash also listed
$_
as a special parameter, but it's now listed among other variables set by the shell (5.2 Bash Variables).$_
is not POSIX and other shells may not support it.$_