├── .travis.yml ├── COPYING ├── README.md ├── array └── test /.travis.yml: -------------------------------------------------------------------------------- 1 | language: sh 2 | before_install: 3 | - sudo apt-get update -qq 4 | - sudo apt-get install -qq zsh mksh pdksh 5 | install: true 6 | script: 7 | - bash ./test 8 | - dash ./test 9 | - ksh ./test 10 | - mksh ./test 11 | - pdksh ./test 12 | - zsh ./test 13 | branches: 14 | only: 15 | - master 16 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # POSIX-compliant implementation of arrays in shell 2 | ![Travis Badge]( https://travis-ci.org/krebs/array.svg ) 3 | 4 | # Usage 5 | 6 | ```bash 7 | # load the library (better than `source`) 8 | $ . array 9 | 10 | # Create a new array 11 | $ A=$(array ' bob' 'ross' '' 'khan') 12 | 13 | # Get the length 14 | $ printf '%s\n' "$A" | array_len 15 | 4 16 | 17 | # Get the element at index 0 18 | $ printf '%s\n' "$A" | array_nth 0 19 | bob% 20 | 21 | # Get the element at index 2 (= empty string) 22 | $ printf '%s\n' "$A" | array_nth 2 23 | % 24 | 25 | # Get the element at (the invalid) index -1 26 | $ printf '%s\n' "$A" | array_nth -1 27 | 28 | # The error code is set to 1 29 | $ echo $? 30 | 1 31 | 32 | # Append item to array 33 | A=$(array_append "$A" 'lolwut') 34 | 35 | # Iterate over the array 36 | $ printf '%s\n' "$A" | while IFS= read element; do printf '%s, \n' "$(printf '%s\n' "$element" | array_element_decode)"; done 37 | bob, 38 | ross, 39 | , 40 | khan, 41 | 42 | # Get the index of the element khan 43 | $ printf '%s\n' "$A" | array_indexof khan 44 | 3% 45 | ``` 46 | 47 | # Notes 48 | 49 | This is a PoC that arrays 'can' be implemented in posix shell, nothing more, nothing less. 50 | If you want full-fledged data structures in your shell i suggest you either rewrite your script in a more powerful scripting language or you use [jq](http://stedolan.github.io/jq/) which lets you work with json in your shell. 51 | 52 | # License 53 | 54 | WTFPL, see COPYING 55 | -------------------------------------------------------------------------------- /array: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | # must use nawk for solaris 4 | # http://cfajohnson.com/shell/cus-faq-2.html#Q24 5 | case `uname` in 6 | SunOS) arrayawkcmd=nawk ;; 7 | *) arrayawkcmd=awk ;; 8 | esac 9 | 10 | ## 11 | # Definitions: 12 | # - An array is a newline separated list of array elements. 13 | # - An array element is an `array_element_encode`d string. 14 | 15 | ## 16 | # Array constructor 17 | # 18 | # Turn arguments into an array. 19 | array () { 20 | for i in "$@" ; do 21 | printf '%s\n' "$i" | array_element_encode 22 | done 23 | } 24 | 25 | ## 26 | # Append items to an array 27 | # 28 | # Pass your array as the first argument: 29 | # array_append "$A" 'next' 'another' 30 | array_append () { 31 | printf '%s\n' "$1" && shift && 32 | for i in "$@" ; do 33 | printf '%s\n' "$i" | array_element_encode 34 | done 35 | } 36 | 37 | 38 | ## 39 | # Get length of array 40 | # 41 | # Pipe your array in: 42 | # printf '%s\n' "$ary" | array_len 43 | array_len () { 44 | wc -l 45 | } 46 | 47 | ## 48 | # Element accessor 49 | # 50 | # Uses first argument as (zero-based) index. 51 | # 52 | # Exit status: 53 | # 0 Success. 54 | # >0 An error occurred. 55 | # 56 | # Pipe your array in: 57 | # printf '%s\n' "$ary" | array_nth $index 58 | array_nth () { 59 | printf '%d' "$1" >/dev/null 2>&1 \ 60 | && "$arrayawkcmd" -v i="$1" ' 61 | BEGIN { code=1 } 62 | NR == i + 1 { print $0; code=0 } 63 | END { exit code } 64 | ' \ 65 | | array_element_decode 66 | } 67 | 68 | ## 69 | # Get the first (zero-based) index of an element. 70 | # 71 | # Exit status: 72 | # 0 Success. 73 | # >0 An error occurred (element not in array). 74 | # 75 | # Pipe your array in: 76 | # printf '%s\n' "$ary" | array_indexof "$element" 77 | array_indexof () { 78 | e=`printf '%s\n' "$1" | array_element_encode` "$arrayawkcmd" ' 79 | BEGIN { e=ENVIRON["e"] ; i = -1 ; code = 1 } 80 | i < 0 && e == $0 { i = NR - 1 ; print i ; code = 0 } 81 | END { exit code } 82 | ' 83 | } 84 | 85 | ## 86 | # Remove the first occurence of an element. 87 | # 88 | # Exit status: 89 | # 0 Success. 90 | # >0 An error occurred (element not in array). 91 | # 92 | # Pipe your array in: 93 | # printf '%s\n' "$ary" | array_remove "$element" 94 | array_remove () { 95 | e=`printf '%s\n' "$1" | array_element_encode` "$arrayawkcmd" ' 96 | BEGIN { e=ENVIRON["e"] ; i = -1 ; code = 1 } 97 | { if (i < 0 && e == $0) { i = NR - 1 ; code = 0 } else { print $0 } } 98 | END { exit code } 99 | ' 100 | } 101 | 102 | ## 103 | # Turn stdin into an array element. 104 | # 105 | # This is similar to urlencode but only for % and \n (and not \0). 106 | # 107 | array_element_encode() { 108 | sed 's/%/%25/g' | sed -e :a -e '$!N; s/\n/%0A/; ta' 109 | } 110 | 111 | ## 112 | # Inverse function of array_element_encode. 113 | # 114 | array_element_decode() { 115 | sed -e 's/%0[aA]/\ 116 | /g' -e 's/%25/%/g' 117 | } 118 | 119 | -------------------------------------------------------------------------------- /test: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | # Test suite for array 4 | 5 | set -e 6 | 7 | . "`pwd`/array" 8 | 9 | # test array_indexof 10 | 11 | A=`array -n ' bob' ross '' khan ross '/\#$^' ro` 12 | 13 | idx=`printf '%s\n' "$A" | array_indexof -n` || { echo 'Index of -n failed' >&2; exit 1; } 14 | if [ "$idx" -ne 0 ] ; then 15 | echo "Index of -n, expected 0, got $idx" >&2 16 | exit 1 17 | fi 18 | 19 | idx=`printf '%s\n' "$A" | array_indexof khan` || { echo 'Index of khan failed' >&2; exit 1; } 20 | if [ "$idx" -ne 4 ] ; then 21 | echo "Index of khan, expected 4, got $idx" >&2 22 | exit 1 23 | fi 24 | 25 | if printf '%s\n' "$A" | array_indexof tim > /dev/null 2>&1 ; then 26 | echo "Index of tim should fail, got `printf '%s\n' "$A" | array_indexof tim`" >&2 27 | exit 1 28 | fi 29 | 30 | idx=`printf '%s\n' "$A" | array_indexof ''` || { echo 'Index of failed' >&2; exit 1; } 31 | if [ "$idx" -ne 3 ] ; then 32 | echo "Index of : expected 3, got $idx" >&2 33 | exit 1 34 | fi 35 | 36 | idx=`printf '%s\n' "$A" | array_indexof ross` || { echo 'Index of ross failed' >&2; exit 1; } 37 | if [ "$idx" -ne 2 ] ; then 38 | echo "Index of ross: expected 2, got $idx" >&2 39 | exit 1 40 | fi 41 | 42 | idx=`printf '%s\n' "$A" | array_indexof '/\#$^'` || { echo 'Index of /\#$^ failed' >&2; exit 1; } 43 | if [ "$idx" -ne 6 ] ; then 44 | echo "Index of /\#$^: expected 6, got $idx" >&2 45 | exit 1 46 | fi 47 | 48 | idx=`printf '%s\n' "$A" | array_indexof ro` || { echo 'Index of ro failed' >&2; exit 1; } 49 | if [ "$idx" -ne 7 ] ; then 50 | echo "Index of ro: expected 7, got $idx" >&2 51 | exit 1 52 | fi 53 | 54 | idx=`printf '%s\n' "$A" | array_indexof ' bob'` || { echo 'Index of bob failed' >&2; exit 1; } 55 | if [ "$idx" -ne 1 ] ; then 56 | echo "Index of bob: expected 1, got $idx" >&2 57 | exit 1 58 | fi 59 | 60 | # test array_nth 61 | 62 | A=`array -e ' bob' ross '' khan ross '/\#$^' ro` 63 | 64 | val=`printf '%s\n' "$A" | array_nth 3` || { echo 'array_nth 3 failed' >&2; exit 1; } 65 | if [ "$val" != '' ] ; then 66 | echo "array_nth that should return the empty string, didn't." >&2 67 | exit 1 68 | fi 69 | 70 | # test array_element_encode, array_element_decode 71 | 72 | E='look 73 | ma 74 | three lines and a %' 75 | expect_encoded_E='look%0Ama%0Athree lines and a %25' 76 | really_encoded_E=`printf '%s\n' "$E" | array_element_encode` 77 | 78 | if [ "$expect_encoded_E" != "$really_encoded_E" ]; then 79 | echo "array_element_encode doesn't do the right thing" >&2 80 | echo " wanted: $expect_encoded_E" >&2 81 | echo " but got: $really_encoded_E" >&2 82 | exit 1 83 | fi 84 | 85 | really_decoded_E=`echo "$really_encoded_E" | array_element_decode` 86 | if [ "$E" != "$really_decoded_E" ]; then 87 | echo "array_element_decode doesn't do the right thing" >&2 88 | echo " wanted: $E" >&2 89 | echo " but got: $really_decoded_E" >&2 90 | exit 1 91 | fi 92 | 93 | A1=`array "$E"` 94 | if [ "`printf '%s\n' "$A1" | array_nth 0`" != "$E" ]; then 95 | echo "array_nth cannot handle encoded elements" >&2 96 | exit 1 97 | fi 98 | 99 | if [ "`printf '%s\n' "$A1" | array_indexof "$E"`" -ne 0 ]; then 100 | echo "array_indexof cannot handle encoded elements" >&2 101 | exit 1 102 | fi 103 | 104 | # test array_append 105 | 106 | A=`array_append "$A" next` 107 | 108 | idx=`printf '%s\n' "$A" | array_indexof next` || { echo 'Index of next failed' >&2; exit 1; } 109 | if [ "$idx" -ne 8 ] ; then 110 | echo "Index of next: expected 8, got $idx" >&2 111 | exit 1 112 | fi 113 | 114 | idx=`printf '%s\n' "$A" | array_indexof ' bob'` || { echo 'Index of bob failed' >&2; exit 1; } 115 | if [ "$idx" -ne 1 ] ; then 116 | echo "Index of bob: expected 1, got $idx" >&2 117 | exit 1 118 | fi 119 | 120 | # test array_len 121 | 122 | len=`printf '%s\n' "$A" | array_len` || { echo 'array_len failed' >&2; exit 1; } 123 | if [ "$len" -ne 9 ] ; then 124 | echo "array_len: expected 9, got $len" >&2 125 | exit 1 126 | fi 127 | 128 | # test array_remove 129 | A=`printf '%s\n' "$A" | array_remove next` 130 | 131 | if printf '%s\n' "$A" | array_indexof next > /dev/null 2>&1; then 132 | echo "Index of next should fail, got `printf '%s\n' "$A" | array_indexof next`" >&2 133 | exit 1 134 | fi 135 | 136 | A=`array first second third first` 137 | A=`printf '%s\n' "$A" | array_remove first` 138 | 139 | idx=`printf '%s\n' "$A" | array_indexof first` || { echo 'Index of failed' >&2; exit 1; } 140 | if [ "$idx" -ne 2 ] ; then 141 | echo "Index of first: expected 2, got $idx" >&2 142 | exit 1 143 | fi 144 | 145 | A=`printf '%s\n' "$A" | array_remove first` 146 | 147 | if printf '%s\n' "$A" | array_indexof first > /dev/null 2>&1; then 148 | echo "Index of first should fail, got `printf '%s\n' "$A" | array_indexof first`" >&2 149 | exit 1 150 | fi 151 | 152 | echo 'Tests successful' 153 | 154 | --------------------------------------------------------------------------------