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
  • 371
  • 1
  • 14
  • Closely related: [How to pop the last positional argument of a bash function or script](https://unix.stackexchange.com/q/611713)? – Quasímodo Jul 26 '21 at 12:07
  • Also related to the issue of constructing arguments to `find`: [Make 'find -regextype egrep' as alias](https://unix.stackexchange.com/q/479298) Also my answer to another question: [How to use find command to list file names but not path?](https://unix.stackexchange.com/a/627870) – Kusalananda Jul 26 '21 at 12:21
  • 1
    Where does the string `isufx: ` disappear to when you echo it? Consider making sure that the code that you show corresponds to the output that you show, or we can never be certain about what code you are _actually_ working with. – Kusalananda Jul 26 '21 at 13:00

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] =
Stéphane Chazelas
  • 522,931
  • 91
  • 1,010
  • 1,501
  • Note that it's important to quote the array element to be unset: otherwise you are subject to [Filename Expansion](https://www.gnu.org/software/bash/manual/bash.html#Filename-Expansion) -- if you have a _file_ named "array1", then `unset array[-1]` will substitute the glob pattern with the filename and attempt to unset the wrong variable. – glenn jackman Jul 26 '21 at 12:58
  • 1
    @glennjackman, yes though that's only true of Bourne-like shells in that list. In `fish`, `[...]` is not a glob operator. – Stéphane Chazelas Jul 26 '21 at 13:00
  • Indeed, a fact that often frustrates me. – glenn jackman Jul 26 '21 at 13:02
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).

Gordon Davisson
  • 4,360
  • 1
  • 18
  • 17
  • 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