1

In Bash, I can use the ${parameter:offset:length} notation to slice elements of an array, but I can't use it in all the same ways as I would when slicing a string. Specifically, I would like to use this syntax to print all but the last n elements of an array:

note: --version output is metasyntax, not the intent of my question

calvin@rose:~ A=($(bash --version |head -1)); echo ${A[@]}
GNU bash, version 4.4.12(1)-release (x86_64-pc-linux-gnu)

calvin@rose:~ echo ${A[3]:0:-8}            # Negative length of substring
4.4.12(1)

calvin@rose:~ echo ${A[@]:2:2}             # Predictable behavior with [@]
version 4.4.12(1)-release                  # Desired output

calvin@rose:~ echo ${A[@]:2:-1}            # Desired input
-bash: -1: substring expression < 0        # Limitation of Bash Arrays?

This behavior is defined under Parameter Expansion:

If parameter is ‘@’, the result is length positional parameters beginning at offset. A negative offset is taken relative to one greater than the greatest positional parameter, so an offset of -1 evaluates to the last positional parameter. It is an expansion error if length evaluates to a number less than zero.

There are two workarounds I currently use:

calvin@rose:~ unset A[-1]; echo ${A[@]:2}  # This works, but I lose data
version 4.4.12(1)-release

calvin@rose:~ echo ${A[@]:2: ${#A[@]}-3 }  # This works, but it gets messy fast
version 4.4.12(1)-release

Is there a better way? The python A[2:-1] kids are making fun of me.

vintnes
  • 2,014
  • 7
  • 16
  • `echo ${A[@]: -3:2}`? Space is important. – Cyrus Jun 18 '19 at 09:02
  • @Cyrus Clever, but requires foreknowledge of the length of the array. My 'predictable behavior' example would be cleaner. – vintnes Jun 18 '19 at 09:04
  • `unset 'A[${#A[@]}-1]' 'A[${#A[@]}-1]'`? – Cyrus Jun 18 '19 at 09:11
  • To remove last two elements from array A. – Cyrus Jun 18 '19 at 09:14
  • How does `echo ${A[@]:2: ${#A[@]}-3 }` get's "messy fast"? `I would like to use this syntax to remove the last n elements from an array` - `A=("${A[@]:0:${#A[@]}-$n}")` ? `unset $(seq -f A[%.0f] $(("${#A[@]}" - n - 1)) $(("${#A[@]}" - 1)))` ? `readarray -t -d '' A < <(printf "%s\0" "${A[@]}" | head -z -n -$n)`? `unset $(printf "A[%s]\n" ${!A[@]} | tail -n $n)`? Any of these are not "messy fast"? – KamilCuk Jun 18 '19 at 09:16
  • 2
    @KamilCuk maybe we have different ideas about what constitutes clean code. – vintnes Jun 18 '19 at 09:21
  • Any awk,sed or cut solution is acceptable for you? like `echo ${A[@]} | cut -d ' ' -f 3-$((${#A[@]}-1))` – lw0v0wl Jun 18 '19 at 10:33
  • @Edvin Using a subshell and an external proc is far less efficient than my current workarounds. – vintnes Jun 18 '19 at 10:54
  • I am aware of that ,this is why I not post an answer, and ask instead. I not believe there is a cleaner/shorter solution than you already have. – lw0v0wl Jun 18 '19 at 11:26

0 Answers0