47

How can I get a slice of $@ in Bash without first having to copy all positional parameters to another array like this?

argv=( "$@" )
echo "${argv[@]:2}";
n.r.
  • 2,243

4 Answers4

66

You can use the same format as for any other array. To extract the 2nd and 3rd elements from $@, you would do:

echo "${@:1:2}"
          - -
          | |----> slice length
          |------> slice starting index 
terdon
  • 242,166
  • 1
    But this seems to work on a char by char basis in v4.1.2, is there a way to do it on a word by word basis? – Alexej Magura Oct 04 '16 at 22:08
  • @AlexejMagura I don't understand what you mean. That acts on the elements of the array. If you have one-character elements, it will work "on characters". If each element is a word, it works on words. Are you maybe trying this on a string and not an array? – terdon Oct 05 '16 at 09:04
  • I'm trying it on a copy of "$@", which I guess might become a string at that point, I'm not sure. – Alexej Magura Oct 05 '16 at 16:45
  • 3
    On bash 4.2.46, "${@:1:2}" actually gives me the 1st and 2nd command line arguments. Meanwhile, "${@:1}" gives me the full command line arguments, and "${@:0}" gives me the script name followed by full command line arguments. – Rockallite Feb 09 '17 at 01:56
  • 2
    @Rockallite well, yes. The 2nd and 3rd elements of the $@ array are the 1st and 2nd arguments. "${@:1}" will print the entire array starting with the 2nd element (the 1st argument) and ${@:0} the entire array starting from the 1st element which is the name of the script. What were you expecting? – terdon Feb 09 '17 at 09:13
  • @terdon However, when I copy it directly like this argv=("$@"), there are only command line arguments, no script name. So "${argv[@]:1:2} will be the actual 2nd and 3rd command line arguments, while ${@:1:2} is the 1st and 2nd command line arguments. – Rockallite Feb 09 '17 at 09:49
  • ...and you mentioned "${@:1:2}" for *2nd and 3rd elements from $@*. Nothing wrong. Just a bit confusing for me. And I want to clarify it. – Rockallite Feb 09 '17 at 09:51
  • @Rockallite yeah, it is confusing, I know. And I just realized I don't understand it as well as I thought I did, myself. The first thing to remember is that arrays in bash (as in most programming languages) start at 0 and not at 1. So the element at index 1 is actually the second and not the first. Now, when you print "$@", you are printing a string, not an array, and apparently bash will remove the script name automatically. Basically, $@ is special and doesn't behave like other arrays. This probably merits its own question. – terdon Feb 09 '17 at 10:50
  • If you are working with an array A and not the argument list, the syntax is echo "${A[@]:1:2}" to get two elements starting at position 1 in array A. As noted above, echo "${A:1:2}" will give two characters from the first element, starting with the character at position 1. The quotes are required if the elements of A contain spaces, if you want to preserve element structure. – Stuart R. Jefferys Nov 13 '17 at 18:33
  • It appears echo "${@:2}" gives the 2nd command-line argument, and onwards, to the very end. Can you add this as an example, and an explanation of it, to your answer too? – Gabriel Staples Aug 16 '21 at 19:23
11

More array slicing examples in bash

In place of using some_array_variable in your code (argv in your case), simply use @ in that variable name's place. The symbol @ represents the input argument array, and you can treat it exactly like you'd treat any other array. The confusion is simply that we are accustomed to seeing it almost always paired with the $ character like this: $@, and so we don't realize that the @ character is the array itself, and can be used alone as an array variable as well.

So, here are some examples:

Slice from the input argument array, @, which is normally seen and accessed simply as $@:

# array slicing basic format 1: grab a certain length starting at a certain
# index
echo "${@:2:5}"
#         │ │
#         │ └────> slice length
#         └──────> slice starting index

array slicing basic format 2: grab all remaining array elements starting at a

certain index through to the end

echo "${@:2}"

└──────> slice starting index

More array reminders to myself:

The following are general array reminders to myself that may benefit others landing on this page too.

# store a slice from an array into a new array
new_array=("${@:4}")

print the entire array

echo "new_array = ${new_array[@]}"

Here is a runnable example of generic array slicing and array element access in bash, inspired by this source:

(Use @ as the input argument array if desired, instead of a below, according to your use case)

a=(one two three four five six)   # define a new array, `a`, with 6 elements
echo "$a"           # print first element of array a
echo "${a}"         # print first element of array a
echo "${a[0]}"      # print first element of array a
echo "${a[1]}"      # print *second* element of array a
echo "${#a[@]}"     # print number of elements in array a
echo "${a[@]:1:3}"  # print 2nd through 4th elements; ie: the 3 elements
                    # starting at index 1, inclusive, so: indices 1, 2, and 3
                    # (output: `two three four`)
echo "${a[@]:1}"    # print 2nd element onward

Run it all

Copy and paste all code chunks above into a file called array_slicing_demo.sh, and mark it executable with chmod +x array_slicing_demo.sh so that you can run it. Or, just download my demo array_slicing_demo.sh file from my eRCaGuy_hello_world repo here.

Then, run it like this:

./array_slicing_demo.sh a b c d e f g h i j k

...and you will see the following output:

b c d e f
b c d e f g h i j k
new_array = d e f g h i j k
one
one
one
two
6
two three four
two three four five six

References

  1. Bash: slice of positional parameters
  2. Single parenthesis in bash variable assignment

Keywords: array access in bash; bash array indexing; access elments in arrays in bash; bash array slicing; print arrays in bash; print array elements in bash

2

I usually do this:

somefunc() {
    local message="$1"
    shift
    echo "message = $message"
    echo "other   = $@"
}
somefunc first second third goforth

which will print:

message = first
other   = second third goforth

You can expand on the concept by shifting after second, third, etc. argument

1

For function parameters, the answer with echo "${@:1:2}" didn't work for me at all. Also, I wanted the slice-off the first element, since it was a different parameter. What did work was:

function foo(){ #takes single param + array of params
  local param1="$1".                   #first param
  local -a tmp=( "${@}" )              #copy all params
  local -a rem_params=( "${tmp[@]:1}") #slice off first:Works!
# local -a rem_params=( "${@[@]:1}"  ) #DID NOT WORK, ERROR
# local -a rem_params=( "{@:1}"      ) #DID NOT SLICE
  echo "${rem_params[@]}"
}

Maybe I'll write to test to see how this works with positional arguments for at the script-level, but no time right now.

TonyH
  • 131