2

When I do something like ARGS="$@", the shell just concatenates the arguments stored in "$@". Is there a way to store "$@" into a variable in pure POSIX shell to be able to use it to call another program or function with the passed arguments?

2
  • 2
    $@ acts as an array. POSIX shell does not support array variables. To re-use all args, just use "$@" as in something_else "$@". Commented Jun 7 at 9:19
  • @pmf that only works at the top level. inside a function, $@ no longer refers to the arguments the script was called with - you can propagate the elements down but it's a bit tedious and probably inefficient and would require every function to know how to handle the extra arguments Commented Jun 7 at 10:54

1 Answer 1

6

The only array-like structure in a POSIX shell is the $@ list whose elements can be accessed independently with $1,$2,$3,... The number of elements available is given by $#.

$@ elements can be modified using set and shift.

The top-level and each function call has its own separate $@ array which is initialised to the arguments received by the script/function when it is called. Using set or shift inside a function does not affect the top-level $@.

It is possible to save all the original elements of $@ somewhere but I don't believe there is way to use the result in a form as simple as bash's "${arr[@]}" syntax. (Individual elements may accessed without too much effort but not, trivially, the array as a whole.)

However, by appropriately reloading/manipulating the elements of $@ it can be used directly, although performing the manipulation is likely to be rather tedious.

A quick search for ways to accomplish the saving found these approaches:

Rich's code from the second link is probably the simplest and looks like:

save(){
    for i do
        printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/"   
    done
    echo " "
}

and is used as:

myarray=$(save "$@")

set -- foo bar baz boo
# ... do stuff with new $@ ...

eval "set -- $myarray"

The eval is safe since $myarray expands to a list of single-quoted strings.

See how-to-use-pseudo-arrays-in-posix-shell-script for a more efficient and arguably easier to understand awk implementation of this idea. Here's the performance difference between the 2 implementations (timed using bash):

$ cat tst.sh
#!/usr/bin/env bash

save_sed(){
    for i do
        printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/"
    done
    echo " "
}

save_awk() {
    LC_ALL=C awk -v q=\' '
        BEGIN {
            for ( i=1; i<ARGC; i++ ) {
                gsub(q, q "\\" q q, ARGV[i])
                printf "%s ", q ARGV[i] q
            }
            print ""
        }
    ' "$@"
}

echo "calling sed in a loop:"
time save_sed >/dev/null $(seq 100)
echo ""
echo "calling awk once:"
time save_awk >/dev/null $(seq 100)

$ ./tst.sh
calling sed in a loop:

real    0m3.403s
user    0m1.014s
sys     0m2.615s

calling awk once:

real    0m0.042s
user    0m0.015s
sys     0m0.031s

The awk versions performance hardly changes when the number of arguments change while the loop+sed version increases/decreases by about a factor of 10 when the number of arguments changes by the same factor of 10.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.