1

I am calling find by constructing an array isufx containing filename suffixes.

Thusly, I have

echo "isufx: ${isufx[*]}"

that results in

-name *.texi -o -name *.org -o

Finally I got the remove the last element in the array (-o) so I can use it with find.

find "$fdir" "${isufx[@]}" 

I ask what technique to use for removing the last element that is more robust, for cases where array index does not start from 0.

Pietru
  • 389
  • 1
  • 17

2 Answers2

9

With recent versions of bash (4.3 or above), you can do:

unset 'array[-1]'

to unset the element with highest indice, like in zsh:

$ bash -c 'a[3]=1 a[12]=2; a[123123]=3; typeset -p a; unset "a[-1]"; typeset -p a'
declare -a a=([3]="1" [12]="2" [123123]="3")
declare -a a=([3]="1" [12]="2")

That also works in ksh93 since ksh93t.

Note that the quotes are necessary as [...] is a glob operator in Bourne-like shells. If there was a file called array1 in the current directory for instance, an unquoted array[-1] would expand to array1, and if there wasn't that would either expand to nothing or to array[-1] or cause an error depending on the shell and glob option settings.

In zsh (where arrays are normal arrays, not those sparse arrays of ksh/bash), beside unset 'array[-1]', you can also do:

array[-1]=()

(same for unsetting any element and shift the ones after it, while unset would set an element to the empty string when it's not the last to keep some level of compatibility with ksh).

In yash (also with normal arrays):

array -d array -1

In fish (also with normal arrays):

set -e array[-1]

In csh (also with normal arrays, and the first shell with array support (since the late 70s!)):

set array[$#array] =
4

You can use array slicing to get all but the last element:

find "$fdir" "${isufx[@]:0:${#isufx[@]}-1}"

Explanation: ${#isufx[@]} gets the number of elements in the array, and adding :0:numelements-1 to the array expansion gets numelements-1 elements starting at #0... which is all but the last one.

You could also simplify it by constructing the array slightly differently: put the extra -o at the beginning (i.e. for each suffix, add "-o" "-name" "*.$suffix" instead of "-name" "*.$suffix" "-o"), and then use "${isufx[@]:1}" to start with element #1 (skipping #0).

  • 1
    In this context, I sometimes find it easier to add an element so it becomes ... -o false – steeldriver Jul 26 '21 at 11:24
  • @steeldriver, -false is not a standard predicate though. – Stéphane Chazelas Jul 26 '21 at 12:58
  • Or change the array building so that you add the -o at the front, but not for the first element. You could work around the lack of -false with some hack, though GNU find seems to warn about -name / and -path "*/"... Or repeat the first or last pattern at the end of the expression, provided you have at least one. – ilkkachu Jul 26 '21 at 13:20
  • @ilkkachu -name '' should be a good approximation of -false. – Stéphane Chazelas Jul 26 '21 at 13:28
  • Funnily enough -name / matches on / in find / -name / -prune -print but GNU find still reports: find: warning: ‘-name’ matches against basenames only, but the given pattern contains a directory separator (‘/’), thus the expression will evaluate to false all the time. Did you mean ‘-wholename’? – Stéphane Chazelas Jul 26 '21 at 13:29
  • @StéphaneChazelas, ooh, -name "" is a good one, and GNU doesn't appear to warn about that. Though wouldn't surprise me if they add a warning to it. And yeah, you're right -name / isn't optimal because it does match /. (Stupid special cases...) So yeah, maybe the warning is on point in that -path / would be clearer, even if they missed the special case with /. – ilkkachu Jul 26 '21 at 13:38