4

Let's say I'd like to remove the first two and last three characters from all elements of an array e.g.

results=( QK9H9UtADCgnG AlaLkCADjQ krsxseW8H1VrU 6nBG94ZbCWQ )

I'd like to end up with

results=( 9H9UtADC aLkCA sxseW8H1 BG94Zb )

other than looping over the elements and using ${element:2:-3} is there a flag or some sort of combination that allows doing something like ${(⭕)results:2:-3} so that substring expansion affects every element instead of affecting the array?


If it matters, in my particular case, the arrays can never have empty elements, elements can not contain any sort of white-space characters and the extraction rule is always L>P+S that is the Length of any element is always greater than the combined length of the Suffix and Prefix that are to be removed.
I'm not interested in rev-ing strings and using text processing tools. I'm currently using a function to loop as I said above, so this isn't a matter of trying to type less, I was just curious if it's possible as I went through the manual and couldn't find a way to do it.

3
  • it looks like you want a functional programming style of processing in the shell, with things like map, reduce and filter? Maybe this: github.com/timonson/bashFunc Commented Jan 7, 2023 at 15:56
  • Related: zsh: map command to array Commented Jan 7, 2023 at 16:52
  • @Stéphane -yes, I forgot to mention that I'm already using a function; that link and this one were very useful back when I wrote my own function; thanks. Commented Jan 9, 2023 at 14:33

2 Answers 2

4

Note that ${string:offset:length} from ksh93 is not the native zsh way, it's only been added recently for compatibility with ksh93 / bash.

In zsh, you rather do $string[first,last] (which predates ksh93).

But in any case, whether it's zsh's $var[first,last] or ksh93-style ${var:offset:length}, those are operators that can be applied both to strings and arrays (in zsh, contrary to bash/ksh, arrays and scalar are two separate variable types where operators can apply differently), giving you a substring in the first case and a subset of elements in the second.

And as far as I'm aware you can't tell zsh that those are to be applied to each string element as opposed to the whole array.

${results[1][3,-4]} (or ${results[1]:2:-3}) works, but ${results[1,2][3,-4]} doesn't as ${results[1,2]} yields an array, not a string so the [3,-4] applies array-wise.

However here, you can take different approaches.

For instance you can apply the ${string#pattern} and ${string%pattern} ksh-style operators to each element with:

results=( ${${results#??}%???} )

Or for removing arbitrary numbers of leading and trailing characters:

set -o extendedglob
results=( ${${results#?(#c5)}%?(#c8)} )

You can also apply arbitrary modifications to each element using the ksh93-style ${array/pattern/replacement}, so for instance:

set -o extendedglob
results=( ${results/(#m)*/$MATCH[3,-4]} )

See also

results=( ${results/(#b)??(*)???/$match[1]} )

Which would only modify the elements that have at least 5 characters (same as results=( "${results[@]/??@(*)???/\1}" ) in ksh93).

Another convoluted approach:

() { results=( ${(e)argv} ); } '${results['{1..$#results}'][3,-4]}'

Where we pass ${results[1][3,-4]} ${results[2][3,-4]} etc to an anonymous function inside which we use the e flag to evaluate them and assign back to the array.

(In all those, use the variants with [@] and quoted if you want to preserve empty elements, including the ones that do become empty after the trimming).

2
  • With a caveat that if an element is short enough to become empty, some of these solutions don't make it empty or remove the empty element. Commented Jan 7, 2023 at 17:15
  • @Gilles'SO-stopbeingevil', see the (use the variants with [@] and quoted if you want to preserve empty elements). note Commented Jan 7, 2023 at 17:15
-1

Here is one method to trim your array elements:

trimmed_array=( $(printf '%s\n' "${results[@]}" | cut -c 3- | rev | cut -c 4- | rev) )

check the new array:

printf '%s\n' "${trimmed_array[@]}"
9H9UtADC
aLkCA
sxseW8H1
BG94Zb

The action of each piped section is easy to check and understand.

3
  • But that only works if the array elements don't contain IFS nor newline characters and in the case of GNU cut at least no multibyte character. Commented Jan 7, 2023 at 16:29
  • @ilkkachu, not to remove a fixed number of characters from both start and end as is required here. Commented Jan 7, 2023 at 16:40
  • @StéphaneChazelas, right, make it one awk then, substr() with length() should do. Commented Jan 7, 2023 at 17:22

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.