Tcl proc to output a list or array - arrays

I am new to Tcl arrays. My question is as follows.
I have a rectangle box with two rows R1 and R2. Each of these rows has 8 different values.
I want to return these 16 values (x and y coordinates) either in a text file or as an list output from a proc. I read some earlier posts about Tcl proc cannot output an array unless we use dict.
So, I will try to draw a picture so u can understand my question better.
R1 x1y1 x2y2 ... x8,y8
R2 x9,y9 ... x16, y16
Expected output when I run the proc either on command prompt or in a file with dummy values as an example
$> (1,2) (2,3) (3,4) ....... (7,8)
(9,10) (10,11) ......... (15,16)
So this is what I tried and I am getting the results that I need. But this is hardcoded for two rows. I want to make it able to detect how many rows are there and then accordingly output the number of rows.
proc getPointList {rect_boundary rowOffset colOffset rowIncr colIncr } {
set cordlist $rect_boundary
set xl [lindex $cordlist 0]
set yl [lindex $cordlist 1]
set xh [lindex $cordlist 2]
set yh [lindex $cordlist 3]
set list "" ;
for {set y [expr {$yh - $colOffset}]} {$y >= [expr {$yl + $colOffset}]} { incr y $colIncr } {
for {set x [expr {$xl + $rowOffset}]} {$x <= [expr {$xh - $rowOffset}]} { incr x $rowIncr } {
set list "$list $x $y" ;
puts "Value of x is: $x"; puts "\t Value of y is: $y" ;
}
}
return $list
}
set rect_boundary {10 15 100 40} # xl yl xh yh
set rowOffset 5
set colOffset 5
set rowIncr 10
set colIncr 15
Some Logic I need to implement in this code based on yh-yl and xh-xl to calculate height and width of rectangle and accordingly output rows
Command to call the proc
$> getPointList $rect_boundary $rowOffset $colOffset $rowIncr $colIncr
Just for your understanding there are eight x,y points inside the rectangle on a particular row.
x offset is the first x point on a row from the left or roght boundary, thereafter all the points are separated by an increment value which I call rowIncr. Same holds true for column.
Expected output : This is what the above code does but it is hardcoded for two rows. I want to increase and implement the logic if the rows and column are variable.
$> R1: (15 40) (25 40) (35 40) (45 40) (55 40) (65 40) (75 40) (85 40) (95 40)
R2: (15 15) (25 15) (35 15) (45 15) (55 15) (65 15) (75 15) (85 15) (95 15)
Rectangle Image for better clarity as this thing wont let me update pictures
__________________________________________________________________________ (100,40)
| |- 5 |
| . . . . . . . . |
| |- 15 |
|-5-. . --10---. . . . . . |
| |
|_________________________________________________________________________|
(10,15)
For Jerry:
Case1 rowIncr 10 colIncr 20
__________________________________________________________________________ (80,40)
| |- 5 |
| . . . . . . . . |
| |- 20 |
|-5-. . --10---. . . . . . |
| |
|_________________________________________________________________________|
(10,10)
Case2 rowIncr 20 colIncr 35
_________________________________________________ (100,70)
| |- 5 |
| . . . . . |
| |- 35 |
|-5-. . --20---. . . |
| | |-5 |
|________________________________________________|
(10,25)
and so on ...

Okay, I think I now understand what you were trying to do, and I think that your proc would have worked for any number of rows after some fixing:
set output [open "output.txt" w]
proc getPointList {rect_boundary rowOffset colOffset plist} {
global output
set cordlist $rect_boundary
set xl [lindex $cordlist 0]
set yl [lindex $cordlist 1]
set xh [lindex $cordlist 2]
set yh [lindex $cordlist 3]
set xpoints [llength [lindex $plist 0]]
set ypoints [llength $plist]
set rowIncr [expr {($xh-$xl-2*$rowOffset)/($xpoints-1)}]
set colIncr [expr {($yh-$yl-2*$colOffset)/($ypoints-1)}]
set count 0
set list ""
for {set y [expr {$yh - $colOffset}]} {$y >= [expr {$yl + $colOffset}]} {incr y -$colIncr} {
for {set x [expr {$xl + $rowOffset}]} {$x <= [expr {$xh - $rowOffset}]} {incr x $rowIncr} {
lappend list "($x,$y)"
}
incr count
puts $output "R$count: [join $list " "]"
set list ""
}
}
set plist {{A B C D E} {A B C D E} {A B C D E} {A B C D E} {A B C D E}}
set rect_boundary {0 0 100 100}
set rowOffset 0
set colOffset 0
getPointList $rect_boundary $rowOffset $colOffset $plist
close $output
I changed the colIncr to put more rows.
In the first loop, I used incr y -$colIncr because this is actually a decrement if you start with the higher y coordinate.
I also changed the output structure to match the one you were looking for. The above snippet returns the coordinates:
R1: (0,100) (25,100) (50,100) (75,100) (100,100)
R2: (0,75) (25,75) (50,75) (75,75) (100,75)
R3: (0,50) (25,50) (50,50) (75,50) (100,50)
R4: (0,25) (25,25) (50,25) (75,25) (100,25)
R5: (0,0) (25,0) (50,0) (75,0) (100,0)
EDIT: Added variable offsets, blank final row and variable columns per row.
proc getPointList {rect_boundary uRowOffset lRowOffset uColOffset lColOffset plist} {
set cordlist $rect_boundary
set xl [lindex $cordlist 0]
set yl [lindex $cordlist 1]
set xh [lindex $cordlist 2]
set yh [lindex $cordlist 3]
set xpoints 0
foreach r $plist {
if {[llength $r] > $xpoints} {set xpoints [llength $r]}
}
set ypoints [llength $plist]
set rowIncr [expr {($xh-$xl-$lRowOffset-$uRowOffset)/($xpoints-1)}]
set colIncr [expr {($yh-$yl-$lColOffset-$uColOffset)/$ypoints}]
set count 0
set list ""
for {set y [expr {$yh - $uColOffset}]} {$y >= [expr {$yl + $lColOffset}]} {incr y -$colIncr} {
set x [expr {$xl + $lRowOffset}]
foreach n [lindex $plist $count] {
lappend list $x $y
incr x $rowIncr
}
incr count
if {$count == $ypoints} {return $list}
}
}
set plist {{A B C D X} {E F G H} {I K L} {M N}}
set qlist 1
foreach n $plist {
set pattern$plist $n
incr qlist
}
set rect_boundary {0 0 100 100}
set upperRowOffset 0
set lowerRowOffset 0
set upperColOffset 0
set lowerColOffset 0
set pointList [getPointList $rect_boundary $upperRowOffset $lowerRowOffset $upperColOffset $lowerColOffset $plist]
set count 1
foreach sub_list $plist {
foreach n $sub_list {
set pattern$count $n
incr count
}
}
set count 1
foreach {a b} $pointList {
set text "pattern$count"
puts "command -point $a,$b -text [set $text]"
incr count
}

It is up to you how to organize the nested lists. In simplest form, return a single list:
set result {x1 y1 x2 y2 ... x16 y16}
Or, you can have a list of two rows:
set result {
{x1 y1 x2 y2 ... x8 y8}
{x9 y9 x10 y10 ... x16 y16}
}
Or, more complex: each pair is a sub-list:
set result {
{ {x1 y1} {x2 y2} ... }
{ {x9 y9} {x10 y10} ... }
}
I don't think you want to use array in this case. FYI, an "array" in TCL is equivalent to a hash in other languages.

Well, with Tcl 8.6
proc parsedata {input} {
lmap a [split $input \n] {
lmap b [split $b \t] {
regexp {x(\d+),?\s*y(\d+)} $b -> c d
list $c $d
}
}
}
Now you can process the data, for example:
foreach line [parsedata $input] {
puts [lmap pair $line {
expr {"([lindex $line 0],[linedex $line 1])"}
}
}

Related

TCl Proc to output list or collection

My Question is as follows:
Objective or problem statement : Find the dots in rectangle that are separated by a distance and store it in a list (collection) which will be my final output
Spec:
We start with an x offset and y offset to begin with and then we keep on incrementing in x and y direction till we reach upper right rectangle boundary.
Expected Output
My rect_boundary is {0 0} {112 96} as llx lry urx ury (lower left x, lower right y, upper right x .... cordinates) It can change each time based on procedural argument , rectangle box can be (00) (200 200) I have used this as an example
x offset for first red box = 5
y offset from first red box to boundary = 5
Distance between 1st and 2nd dot = 45, Distance between 2 and 3 = 45 , Distance between 4 and 5 = 45, Distance between 6 and 7 = 45 , so basically all horizontal points are separated by a distance of 45, that will always stay constant.
1 , 2 and 4 form a triangle, so "4 is located exactly in center of 1 and 2 but vertical distance of 45.
Example: 1st point = (5 5 ) , 2nd point = (50 5) so position of "4th point" will be ((5+50/2), 5+45) i.e., (27.5 50)
Position of 5th point on row 2 will be dependent on 4 as their cordinate will be (27.5+45, 50) = (72.5 50)
Now position of 6 and 7 point in rectangle will also be dependent on point 4
Center Point = Box 4 (x, y+45) = (27.5 95) and Box 6 = {(27.5 -45/2) 95) = (5, 95)
i.e Point 6 will be exactly on top of Point 1 = (5, 95)
What I have tried so far and it is giving slightly different output
set output [open "output.txt" w]
proc getPointList {rect_boundary xOffset yOffset } {
global output
set cordlist $rect_boundary
set xl [lindex $cordlist 0]
set yl [lindex $cordlist 1]
set xh [lindex $cordlist 2]
set yh [lindex $cordlist 3]
set xIncr 45
set yIncr 45
set x_margin 2
set y_margin 2
set count 0
set list ""
for {set y [expr {$yl +$yOffset}]} {$y <= $yh} {incr y $yIncr} {
for {set x [expr {$xl + $xOffset}]} {$x <= $xh } {incr x $xIncr} {
set point_lookup_window [list [list [expr {$x - $x_margin}] [expr {$y - $y_margin}]] [list [expr {$x + $x_margin}] [expr {$y + $y_margin}]]]
set point_list [index collection [(#some logic_ get_boxes -within $point_lookup_window] 0]
}
incr count
puts $point_list
}
}
set rect_boundary {0 0 111 96}
set xOffset 5
set yOffset 5
getPointList $rect_boundary $xOffset $yOffset
close $output
Note: Rectanglular boundary and offsets will change as they are procedural arguments
Above code gives me equidistant points (have shown dots) as follows, so need the calculation from 2nd row onwards working
Please help expedite and feel free to ask questions.
For each row, you're initializing x at the same value, $xl + $xOffset.
That's what you want for the 1st, 3rd, 5th, ... rows, but x in the even rows should be initialized by an extra half of $xIncr.
I see you're setting a count variable which will keep track of the odd/even rows. You can use this variable to make odd/even rows different.
for {set y [expr {$yl +$yOffset}]} {$y <= $yh} {incr y $yIncr} {
# Define x_start differently on odd/even rows
if {$count % 2 == 0} {
set x_start [expr {$xl + $xOffset}]
} else {
set x_start [expr {$xl + $xOffset + $xIncr/2}]
}
# Iterate x as usual.
for {set x $x_start} {$x <= $xh } {incr x $xIncr} {
...
...
}
incr count
puts $point_list
}

retrieving numerical index from an array in tcl

I'm trying to retrieve a list of numbers from an array (in a txt file) and to do mathematical operations on them then to set them back in a list of the same format.
The txt file looks like this (format is fixed):
1000 2000 3000 4000
I want to read that txt file then to create another list which will be saved in a different text file:
1010 1100 2010 2100 3010 3100 4010 40100 # which is basically adding 10 and 100 to each index.
Here what I've done so far (I'm a very beginner in tcl !)
set zonefile [open "mytxtfile.txt" r]
gets $zonefile
set my_list [gets $zonefile]
puts $my_list
# Check that there is at least one location defined
if { [llength $my_list] == 0 } {
error "No domain are defined in the system parameters workbook"
} else {
puts "OK !"
}
puts [llength my_list]
# this returns 1, which from my understanding means I only have 1 value in my array instead of 4
set c1 [string range $my_list 0 3]
set c2 [string range $my_list 5 8]
set c3 [string range $my_list 10 13]
set c4 [string range $my_list 15 18]
array set domain_list ([lindex $c1 0] [lindex $c2 0])
# I thought this would create an array of 1000 2000 3000 4000 but this doesn't work
Thanks a lot !
I think you're making it more complicated than it needs to be. You don't need to know indexes of elements at all, just use foreach and lappend to build the output array:
set in [open input.txt r]
set a {}
foreach n [split [read $in] " "] {
lappend a [expr {$n + 10}] [expr {$n + 100}]
}
close $in
set out [open output.txt w]
puts $out $a
close $out
If input.txt is:
1000 2000 3000 4000
output.txt will be
1010 1100 2010 2100 3010 3100 4010 4100

Array looping with numbers along with key or value in TCL for Report structure

I need to print a report with dynamic row and dynamic columns as like below.
apple bannana pineapple
Total 47 Total 376 Total 210
a : 3 i : 10 m : 45
b : 4 j : 33 o : 67
c : 18 k : 245 n : 98
d : 22 l : 45
x : 43
I thought to take the llength of all the list and iterate the for loop with
the highest llength of the list. In the above example it is 5 times.
My doubt is, how I can print the particular pair in the list with the number I
get from the "for loop".
For example, In the first iteration, I need to select the first item of all
the fruits and print. May be like fruit_${type}[1]. Not sure how I can do.
Please advise.
#!/bin/sh
# \
exec tclsh "$0" "$#"
set fruit_apple {{a 3} {b 4} {c 18} {d 22}}
set fruit_bannana {{i 10} {j 33} {k 245} {l 45} {x 43}}
set fruit_pineapple {{m 45} {o 67} {n 98}}
set fruit {apple bannana pineapple}
puts $fruit_apple
foreach type $fruit {
puts $type
foreach pair [set fruit_${type}] {
set key [lindex $pair 0]
set value [lindex $pair 1]
puts "$key : $value"
}
}
You have to do a lot of work to format a report vertically like this:
#! /usr/bin/env tclsh
set fruit_apple {{a 3} {b 4} {c 18} {d 22}}
set fruit_bannana {{i 10} {j 33} {k 245} {l 45} {x 43}}
set fruit_pineapple {{m 45} {o 67} {n 98}}
set fruit {apple bannana pineapple}
set maxl [expr {max([llength $fruit_apple], [llength $fruit_bannana], [llength $fruit_pineapple])}]
set fmt "%s\t%s\t%s\t"
foreach type $fruit {
puts -nonewline [format $fmt "" $type ""]
set total($type) 0
foreach pair [set fruit_${type}] {
lassign $pair key value
incr total($type) $value
}
}
puts ""
foreach type $fruit {
puts -nonewline [format $fmt Total "" $total($type)]
}
puts ""
for {set i 0} {$i < $maxl} {incr i} {
foreach type $fruit {
set pair [lindex [set fruit_$type] $i]
if {[llength $pair] == 2} {
puts -nonewline [format $fmt [lindex $pair 0] : [lindex $pair 0]]
} else {
puts -nonewline [format $fmt "" "" ""]
}
}
puts ""
}
output:
apple bannana pineapple
Total 47 Total 376 Total 210
a : 3 i : 10 m : 45
b : 4 j : 33 o : 67
c : 18 k : 245 n : 98
d : 22 l : 45
x : 43
It's marginally easier to transform your data into better data structures. Here, using arrays:
#! /usr/bin/env tclsh
set fruit_apple {{a 3} {b 4} {c 18} {d 22}}
set fruit_bannana {{i 10} {j 33} {k 245} {l 45} {x 43}}
set fruit_pineapple {{m 45} {o 67} {n 98}}
set fruit {apple bannana pineapple}
array set keys {}
array set values {}
array set total {}
set maxl 0
foreach type $fruit {
set l [set fruit_$type]
if {[llength $l] > $maxl} {set maxl [llength $l]}
set total($type) 0
foreach pair $l {
lassign $pair key value
lappend keys($type) $key
lappend values($type) $value
incr total($type) $value
}
}
set fmt "%s\t%s\t%s\t"
foreach type $fruit {
puts -nonewline [format $fmt "" $type ""]
}
puts ""
foreach type $fruit {
puts -nonewline [format $fmt Total "" $total($type)]
}
puts ""
for {set i 0} {$i < $maxl} {incr i} {
foreach type $fruit {
if {$i < [llength $keys($type)]} {
puts -nonewline [format $fmt [lindex $keys($type) $i] : [lindex $values($type) $i]]
} else {
puts -nonewline [format $fmt "" "" ""]
}
}
puts ""
}

How to Create a list with Missing Numbers in a non continious list of numbers using TCL

I wanted to create a list of numbers with missing numbers in a given list as provided in the example below
Existing list { 1,3, 5, 9 , 13, 15}
Resultant list { 2,4,6,7,8,10,11,12,14}
Extended TCL has the function intersect3 which as one of its return values gives a list of A-B. You could intersect your list with a list of all possible numbers that span your list.
If you don't use Extended TCL, you'll have to implement something yourself.
I hardly ever use TCL, so maybe there's a better way, but the basic approach is to just sort the list, then run through it and find the missing values:
#!/usr/bin/tclsh
set A {1 3 5 9 13 15}
set A [lsort -integer $A]
set B {}
set x 0
set y [lindex $A $x]
while {$x < [llength $A]} {
set i [lindex $A $x]
while {$y < $i} {
lappend B $y
incr y
}
incr x
incr y
}
puts $B
Output:
2 4 6 7 8 10 11 12 14
paddy's answer looks pretty good. This is a bit shorter, assumes the list is already sorted.
package require Tcl 8.5
set A {1 3 5 9 13 15}
set result [list]
for {set i [lindex $A 0]; incr i} {$i < [lindex $A end]} {incr i} {
if {$i ni $A} {
lappend result $i
}
}
See http://tcl.tk/man/tcl8.5/TclCmd/expr.htm#M15 for the "ni" operator.

How to slice an array in Bash

Looking the "Array" section in the bash(1) man page, I didn't find a way to slice an array.
So I came up with this overly complicated function:
#!/bin/bash
# #brief: slice a bash array
# #arg1: output-name
# #arg2: input-name
# #args: seq args
# ----------------------------------------------
function slice() {
local output=$1
local input=$2
shift 2
local indexes=$(seq $*)
local -i i
local tmp=$(for i in $indexes
do echo "$(eval echo \"\${$input[$i]}\")"
done)
local IFS=$'\n'
eval $output="( \$tmp )"
}
Used like this:
$ A=( foo bar "a b c" 42 )
$ slice B A 1 2
$ echo "${B[0]}" # bar
$ echo "${B[1]}" # a b c
Is there a better way to do this?
See the Parameter Expansion section in the Bash man page. A[#] returns the contents of the array, :1:2 takes a slice of length 2, starting at index 1.
A=( foo bar "a b c" 42 )
B=("${A[#]:1:2}")
C=("${A[#]:1}") # slice to the end of the array
echo "${B[#]}" # bar a b c
echo "${B[1]}" # a b c
echo "${C[#]}" # bar a b c 42
echo "${C[#]: -2:2}" # a b c 42 # The space before the - is necesssary
Note that the fact that a b c is one array element (and that it contains an extra space) is preserved.
There is also a convenient shortcut to get all elements of the array starting with specified index. For example "${A[#]:1}" would be the "tail" of the array, that is the array without its first element.
version=4.7.1
A=( ${version//\./ } )
echo "${A[#]}" # 4 7 1
B=( "${A[#]:1}" )
echo "${B[#]}" # 7 1
Array slicing like in Python (From the rebash library):
array_slice() {
local __doc__='
Returns a slice of an array (similar to Python).
From the Python documentation:
One way to remember how slices work is to think of the indices as pointing
between elements, with the left edge of the first character numbered 0.
Then the right edge of the last element of an array of length n has
index n, for example:
```
+---+---+---+---+---+---+
| 0 | 1 | 2 | 3 | 4 | 5 |
+---+---+---+---+---+---+
0 1 2 3 4 5 6
-6 -5 -4 -3 -2 -1
```
>>> local a=(0 1 2 3 4 5)
>>> echo $(array.slice 1:-2 "${a[#]}")
1 2 3
>>> local a=(0 1 2 3 4 5)
>>> echo $(array.slice 0:1 "${a[#]}")
0
>>> local a=(0 1 2 3 4 5)
>>> [ -z "$(array.slice 1:1 "${a[#]}")" ] && echo empty
empty
>>> local a=(0 1 2 3 4 5)
>>> [ -z "$(array.slice 2:1 "${a[#]}")" ] && echo empty
empty
>>> local a=(0 1 2 3 4 5)
>>> [ -z "$(array.slice -2:-3 "${a[#]}")" ] && echo empty
empty
>>> [ -z "$(array.slice -2:-2 "${a[#]}")" ] && echo empty
empty
Slice indices have useful defaults; an omitted first index defaults to
zero, an omitted second index defaults to the size of the string being
sliced.
>>> local a=(0 1 2 3 4 5)
>>> # from the beginning to position 2 (excluded)
>>> echo $(array.slice 0:2 "${a[#]}")
>>> echo $(array.slice :2 "${a[#]}")
0 1
0 1
>>> local a=(0 1 2 3 4 5)
>>> # from position 3 (included) to the end
>>> echo $(array.slice 3:"${#a[#]}" "${a[#]}")
>>> echo $(array.slice 3: "${a[#]}")
3 4 5
3 4 5
>>> local a=(0 1 2 3 4 5)
>>> # from the second-last (included) to the end
>>> echo $(array.slice -2:"${#a[#]}" "${a[#]}")
>>> echo $(array.slice -2: "${a[#]}")
4 5
4 5
>>> local a=(0 1 2 3 4 5)
>>> echo $(array.slice -4:-2 "${a[#]}")
2 3
If no range is given, it works like normal array indices.
>>> local a=(0 1 2 3 4 5)
>>> echo $(array.slice -1 "${a[#]}")
5
>>> local a=(0 1 2 3 4 5)
>>> echo $(array.slice -2 "${a[#]}")
4
>>> local a=(0 1 2 3 4 5)
>>> echo $(array.slice 0 "${a[#]}")
0
>>> local a=(0 1 2 3 4 5)
>>> echo $(array.slice 1 "${a[#]}")
1
>>> local a=(0 1 2 3 4 5)
>>> array.slice 6 "${a[#]}"; echo $?
1
>>> local a=(0 1 2 3 4 5)
>>> array.slice -7 "${a[#]}"; echo $?
1
'
local start end array_length length
if [[ $1 == *:* ]]; then
IFS=":"; read -r start end <<<"$1"
shift
array_length="$#"
# defaults
[ -z "$end" ] && end=$array_length
[ -z "$start" ] && start=0
(( start < 0 )) && let "start=(( array_length + start ))"
(( end < 0 )) && let "end=(( array_length + end ))"
else
start="$1"
shift
array_length="$#"
(( start < 0 )) && let "start=(( array_length + start ))"
let "end=(( start + 1 ))"
fi
let "length=(( end - start ))"
(( start < 0 )) && return 1
# check bounds
(( length < 0 )) && return 1
(( start < 0 )) && return 1
(( start >= array_length )) && return 1
# parameters start with $1, so add 1 to $start
let "start=(( start + 1 ))"
echo "${#: $start:$length}"
}
alias array.slice="array_slice"
At the risk of beating a dead horse, I was inspired by #jandob's answer and made this version that
Is simpler (doesn't have so much shift logic or rewriting of variables as often).
Respects quoted strings without dealing with IFS (-r mode only).
Allows the user to specify [start, end) slicing or [start, length] slicing via -l flag.
Allows you to echo the resulting array (default behavior), or "return" it into a new array for use in the calling parent (via -r slicedArray).
Note: namerefs are only supported in Bash >= 4.3. To support earlier versions of Bash (i.e. Mac without Brew's bash), you'll need to use indirection instead: use a temp var to access array parameters, e.g. declare arrValuesCmd="$1[#]"; declare arr=("${!arrValuesCmd}"), and use eval for return values, e.g. eval $retArrName='("${newArr[#]}")' (note the single quotes around the array declaration).
array.slice() {
# array.slice [-l] [-r returnArrayName] myArray 3 5
# Default functionality is to use second number as end index for slice (exclusive).
# Can instead use second number as length by passing `-l` flag.
# `echo` doesn't maintain quoted entries, so pass in `-r returnArrayName` to keep them.
declare isLength
declare retArrName
declare OPTIND=1
while getopts "lr:" opt; do
case "$opt" in
l)
# If `end` is slice length instead of end index
isLength=true
;;
r)
retArrName="$OPTARG"
;;
esac
done
shift $(( OPTIND - 1 ))
declare -n arr="$1"
declare start="$2"
declare end="$3"
declare arrLength="${#arr[#]}"
declare newArr=()
declare newArrLength
# Bash native slicing:
# Positive index values: ${array:start:length}
# Negative index values: ${array: start: length}
# To use negative values, a space is required between `:` and the variable
# because `${var:-3}` actually represents a default value,
# e.g. `myVar=${otherVal:-7}` represents (pseudo-code) `myVar=otherVal || myVar=7`
if [[ -z "$end" ]]; then
# If no end is specified (regardless of `-l`/length or index), default to the rest of the array
newArrLength="$arrLength"
elif [[ -n "$isLength" ]]; then
# If specifying length instead of end-index, use native bash array slicing
newArrLength="$(( end ))"
else
# If specifying end-index, use custom slicing based on a range of [start, end):
newArrLength="$(( end - start ))"
fi
newArr=("${arr[#]: start: newArrLength}")
if [[ -n "$retArrName" ]]; then
declare -n retArr="$retArrName"
retArr=("${newArr[#]}")
else
echo "${newArr[#]}"
fi
}
Examples:
myArray=(x y 'a b c' z 5 14) # length=6
array.slice myArray 2 4
# > a b c z
array.slice -l myArray 3 2
# > z 5
# Note: Output was manually quoted to show the result more clearly.
# Actual stdout content won't contain those quotes, which is
# why the `-r returnArray` option was added.
array.slice -r slicedArray myArray -5 -3 # equivalent of [2, 4)
# > (null)
echo -e "myArray (length=${#myArray[#]}): ${myArray[#]} \nslicedArray (length=${#slicedArray[#]}): ${slicedArray[#]}"
# > myArray (length=6): x y 'a b c' z 5 14
# > slicedArray (length=2): 'a b c' z
array.slice -lr slicedArray myArray -5 3 # length instead of index, equivalent of [2, 5)
# > (null)
echo -e "myArray (length=${#myArray[#]}): ${myArray[#]} \nslicedArray (length=${#slicedArray[#]}): ${slicedArray[#]}"
# > myArray (length=6): x y 'a b c' z 5 14
# > slicedArray (length=3): 'a b c' z 5

Resources