1

I have a rather complex shell script which processes several list of values which are read from a file as space separated values e.g.

SET1="value1 value2 value3" for i in ${SET1}; do ... 

I now want to create a similarly formatted list in the script to write out. However if I do (for example) this:

DISCOVERED='' DISCOVERED+=( us-east-1 ) DISCOVERED+=( us-east-2 ) DISCOVERED+=( us-west-1 ) for REGION in ${DISCOVERED} ; do echo $REGION done 

I get no output. I do get output if I specify in ${DISCOVERED[@]}. It appears I am working with different data types in SET1 and DISCOVERED.

I can easily append to a string with space-separated values, but I end up with either a leading or trailing space which needs cleaned up:

function append_discovered { local VALUE="$1" if [ -z "${DISCOVERED}" ] ; then DISCOVERED="$VALUE" else DISCOVERED="${DISCOVERED} ${VALUE}" fi } 

....but this seems rather cumbersome.

I could treat my output variable as an array - but then I either need to convert it back (DISCOVERED="${DISCOVERED[@]}") at appropriate places to use a different construct for iterating through this list than I have for the other lists.

What is the data type for my input data (e.g. $SET1 above) if not an array?

Is there a neater way to append this list and keep the same data type?

1
  • if you're happy with using word splitting on the string (i.e. that for REGION in ${DISCOVERED}), then a leading or trailing space doesn't matter, it'll disappear during word splitting. So in that case, you might as well use DISCOVERED+=" us-east-1" etc. to append to it. But, seriously, it'd be much cleaner if you just used an array all the way, and would actually work even in the future if/when you need values that themselves contain whitespace or glob characters. Commented Jul 10, 2023 at 19:55

2 Answers 2

3

It appears I am working with different data types in SET1 and DISCOVERED

Indeed. SET1 is a string. It happens that you interpret the string value as being several space-separated elements. DISCOVERED on the other hand is an array with several elements.

set1='this is a string of seven words' discovered=('this' 'is' 'an' 'array' 'with' 'seven' 'elements') echo "$set1" echo "${discovered[@]}" 

One problem with your SET1 code at the top of your question is that each element will be globbed and expanded. So if you have a file called onebananatwo in your current directory and you encounter an element one*, the globbing will expand one* to become onebananatwo. (Preventing this is why double-quoting variables is always strongly recommended.)

I can easily append to a string with space-separated values, but I end up with either a leading or trailing space which needs cleaned up

You can use a conditional expansion to insert the space only if the string is not empty:

append_discovered() { local value=$1 discovered="$discovered${discovered:+ }$value" } 

(Notice I've removed the non-standard function keyword and lowercased the variable names so that they don't unexpectedly clash with reserved variables.)

Here's what's happening with $discovered${discovered:+ }$value:

  • When $discovered is the empty string, ${discovered:+ } also returns an empty string, and so the result is just $value
  • When $discovered has a value, ${discovered:+ } returns a space, and so the result is a string comprising $discovered and $value separated by a single space

Going forwards I'd recommend you consider using an array for array data types rather than space-separated elements in a string, but I appreciate that retro-fitting this into an existing script may not be cost-effective or sensible.

4
  • The variable expansion is neater than if..else - ta Commented Jul 10, 2023 at 23:23
  • ....function is deprecated? Not on my man page, and Google seems confused too. Got a reference for this? Commented Jul 10, 2023 at 23:28
  • @symcbean, I don't think it's deprecated in the sense of being obsolete and being phased out, but it is non-standard and unnecessary in Bash. There's a difference between func() { ... } and function func { ... } in ksh, but not in other shells AFAIK (and function func() { ... } is an error there). Commented Jul 11, 2023 at 6:41
  • @symcbean I've replaced deprecated with non-standard. In hindsight that's probably a better choice or word Commented Jul 11, 2023 at 10:51
2

In my Mac's bash 5.x your first syntax works:

DISCOVERED='' DISCOVERED+=( us-east-1 ) DISCOVERED+=( us-east-2 ) DISCOVERED+=( us-west-1 ) echo ${#DISCOVERED[@]} printf '"%s" ' "${DISCOVERED[@]}" echo 

The output is:

4 "" "us-east-1" "us-east-2" "us-west-1" 

Perhaps you expected the DISCOVERED='' statement to delete all array elements instead of setting the first element to an empty string, so your attempts to expand/return the first array element made it appear the array was empty?

If you want to ensure the array variable is cleared (no elements), I suggest DISCOVERED=() or unset DISCOVERED.

Edited to add:

For generating a delimited string from the array elements, I usually use a simple two-liner. This example uses ':' as the delimiter character:

printf -v SET1 '%s:' "${DISCOVERED[@]}" SET1="${SET1%:}" echo $SET1 

(I didn't include the empty first array element) The output:

us-east-1:us-east-2:us-west-1 

It uses a property of the built-in printf to iterate the array elements, appending the delimiter to each one. (Hooray, I don't have to write a loop!) The second line removes the final delimiter. This will also work with the space character as a delimiter, but that's easier done as SET1="${DISCOVERED[@]}". It's not very elegant having the second line to strip the last delimiter, but it works for many use cases, it's simple, quick, and doesn't call external commands.

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.