Loop through array, assign new value to variable in array - arrays

In my script I ask the user for some input and save it into some variables. Before using the strings in the variables I want to cut them if they are too long and add an ellipsis at the end.
So I put all of the variables in an array and send them through a loop and an if statement and reasign the new value to the variable in the array. I have tried many ways and none have worked. The following being an example:
preset1="Short string"
preset2="Not long very string"
preset3="A very long string here which we will then cut"
presets=("${preset1}" "${preset2}" "${preset3}")
for x in "${presets[#]}"; do
if [[ "${#x}" -gt 20 ]]; then
y="${x:0:20}..."
presets[$x]="$y"
fi
done
Please help!

You have to loop over indexes of your array in order to change the values:
for x in "${!presets[#]}"; do
str=${presets[$x]}
(( ${#str} > 20 )) && presets[$x]="${str:0:20}..."
done
Works for associative and sparse arrays as well.
For variety, you can also use only parameter expansion like this:
for x in "${!presets[#]}"; do
str=${presets[$x]}
suffix=${str:20}
presets[$x]=${str:0:20}${suffix:+...}
done

You need to use the array[i] syntax to assign to array elements:
for ((i = 0; i < ${#presets[#]}; ++i)); do
x=${presets[i]}
if [[ "${#x}" -gt 20 ]]; then
y="${x:0:20}..."
presets[i]="$y"
fi
done

Related

Inconsistencies when removing elements from a Bash array

When using bash arrays, you can remove an element in the following manner:
unset array[i] # where i is the array index
The problem with this is after the unset ${array[#]] is not truly valid. Yes
it does give you the number of active array elements, but not the actual array
depth. The index of the removed index in the array still exists. It simply has
been set inactive/null. For example:
declare -a array=( a b c d e )
unset array[2]
for ((i=0; i < ${#array[#]}; i++)) ; do
echo "$i: ${array[$i]}"
done
outputs the following:
0: a
1: b
2:
3: d
array[2] is still there but set to null or inactive
array[4] (e) does not show because ${#array[#]} is the number of active
array elements and not the true array element depth. This gets very messy very
quickly each time an element is unset
As an example consider the following code:
# array contains 0 or more strings
# remove looks for and removes a given string
remove () {
local str=$1
for (( i = 0 ; i < ${#array[#]}; i++ )) ; do
if [[ "${array[$i]}" == "$str" ]] ; then
unset array[$i]
return 0
fi
done
echo "$str: not registered"
return 0
}
This is only valid the first time remove is called. After that, valid
elements may be missed.
One fix for this is to added the following line after the unset:
unset array[$i]
+ array=( "${array[#]}" )
This re-initializes array with the element completely removed.
The problem, this feels kludgy.
So my questions are this:
1) is there a way of getting the true array element depth?
2) is there a way of detecting the end of an array when iterating through it?
3) is there another cleaner solution?
Understanding Why for ((i=0; i<${#array[#]}; i++)) is broken
There's no such thing as "true array element depth" in the sense that you're asking for here. Bash arrays aren't really arrays -- they're hash maps (numerically indexed for regular arrays, indexed by strings for bash 4.0's new "associative arrays"). As such, there's absolutely no reason for the keys to start from 0 -- you can have an array like the following: declare -a array=( [1000]="one thousand" [2000]="two thousand" [3000]="three thousand" ), and its length is exactly 3; there aren't a bunch of NUL/empty elements sitting between those items (looked up with keys 1000, 2000 and 3000, respectively).
Removing Elements From A Sparse Array Safely
Iterate over indices, if you want to remove by index. Whereas "${array[#]}" iterates over items in the array, "${!array[#]}" (note the !) iterates over by the indices by which those items can be looked up.
As you've observed, it's unsafe to assume that the indices range from 0 to the total number of items in an array, as bash arrays are allowed to be sparse -- but there's no reason to write your code to make that assumption in the first place.
for array_idx in "${!array[#]}"; do
unset "array[$array_idx]"
done

Remove elements from bash array by element property

In bash, if I have an array of file names which I retrieve from a file like this:
readarray files < $MY_DIR/my_file_list.cfg
How can i remove any elements in the array files which are say shorter than four characters long?
Loop about every element of your array and check its element length and remove element if necessary.
for ((i=0;i<=${#files[#]};i++)); do
[[ ${#files[$i]} -lt 4 ]] && unset files[$i]
done
declare -p
${#files[#]}: number of elements in array files
${#files[$i]}: length of element $i
-lt: arithmetic test less-than
Test code, using bash substring expansion:
printf "abc\nabcdefg\na\nabcd\n" | while read x ; do [ "${x:4}" ] && echo $x ; done
Output:
abcdefg
abcd
Therefore:
Pure bash code:
readarray files < <(while read x ; do [ "${x:4}" ] && echo $x ; done < \
$MY_DIR/my_file_list.cfg )
Simpler grep aided code:
readarray files < <(grep '^....' $MY_DIR/my_file_list.cfg)
These methods waste no array memory on too-short entries. The worst case would be if my_file_list.cfg contained only one long name, but billions of short names. (Billions of short names might exhaust bash's memory, or at least lead to swapping, or thrashing.)

How to iterate dynamically in an array in bash

I have an array in my script which I want to use it in for, like this:
for j in "${list[#]}"
do
func $j
done
In function func, sometimes another member will add to the list array, but the for iterates as many time as it was initiated(before the for started)
I want "for" to iterate based on the updated array content
some lines of that function:
if [ $s1 -gt 0 ]
then
(( k = $k +1 ))
list[$k]=$id2
fi
As Eric Renouf said, modifying the list you're working on can be tricky. As long as you're only appending new elements (to the end of the list), and just want those new elements included in the iteration, you can use something like this:
for ((i=0; i<${#list[#]}; i++)); do
#...
if (( s1 > 0 )); then
list+=( "$id2" )
fi
done
Since the length of the list (${#list[#]}) is recalculated every time around, the loop will include new elements. Also, the +=( ) syntax guarantees you're always strictly appending.
It appears that k is the index of the last element, meaning your function only appends items to the end of the list. It seems the best option is to iterate while a separate counter is less than k.
i=0
while (( i < k )); do
j=${list[i]}
func "$j"
((i++))
done

Iterating through an array of strings, and removing parts of their values in Bash?

How to iterate through an array of strings, and remove parts of their values? (BASH)
I have an array populated with a list of sites saved as strings, and I want to grab the sections of them that are to the left of '.com.au' and save them to a new array. There are also a couple of items in the string that don't end with '.com.au' and I want to ignore them completely.
To make it a bit easier to read, I've removed most of the code and only left in what I think will be relevant to the question:
#!/bin/bash
full_array(*); declare -p full_array
edited_array=()
for x in ${full_array[#]};
#If x ends with '.com.au'
#Remove '.com.au' from x
#Save output to edited_array
#Else
#Skip item
done
Will I have to use regex to do this? If so, does anyone know of any good tutorials online that would be worth checking out?
This should work:
for x in ${full_array[#]};do
[[ $x == *.com.au ]] && edited_array+=("${x%.com.au}")
done
if [[ $x == *.com.au ]]; then
edited_array+=( "${x%.com.au}" )
fi
Inside bash double brackets, the == operator is a pattern-matching operator. Then, just use shell parameter expansion to remove the domain and append to the array with +=

bash fill an array of integers from arguments and sort it

I looked everybody but I'm stuck with this code.
For example :
The user calls ./array.sh 3 5 6 2 1
I am supposed to sort (i thought bubble sort) and print the array sorted.
#! /bin/bash
tab=( $# )
define -i temp
for ((i = ${#tab[*]-1 ; i >= 0 ; i--)) ; do
for ((j = 0 ; i - 1; j++)) ; do
if [ ${tab[$i]} > ${tab[$i+1]} ] then
$temp = ${tab[$i+1]
${tab[$i+1]} = ${tab[$i]}
${$tab[$i]} = $temp
fi
done
echo ${tab[*]} #print the array
But bash is not happy with that, he keeps tellin me that I cannot assing values like that.
What do I do wrong ? Can you help me please ? I looked in a lot of places but there is no way to find the solution.
Thanks you in advance guys.
You cannot assign a value to a value (e.g. ${tab[$i+1]} = ${tab[$i]}); you must assign to a name or to an array element. Also, you may not have space on either side of the equals sign (=) in a bash assignment, except in numeric context.
And you dropped some closing braces.
And you misspelled "declare".
And you need a semicolon or newline between your if condition and your then.
And you didn't terminate your inner loop.
And you reference a non-existent array element via ${tab[$i+1]}.
And your inner loop has a constant as its termination condition.
And ${#tab[*]-1} incorrectly attempts to do math inside the braces delimiting the variable reference.
And you referenced the wrong index variable (consistently) in your inner loop.
And > is a redirection operator, not greater than, except in numeric context.
Once you clear up that multitude of errors, you end up with
#! /bin/bash
tab=( $# )
declare -i temp
for ((i = ${#tab[*]} - 1; i > 0 ; i--)) ; do
for ((j = 0 ; $j < $i; j++)) ; do
if [ ${tab[$j]} -gt ${tab[$j+1]} ]; then
temp=${tab[$j+1]}
tab[$j+1]=${tab[$j]}
tab[$j]=$temp
fi
done
done
echo ${tab[*]} #print the array
which actually works.
Your variable assignment syntax is all wrong.
First, you don't put $ before the variable being assigned.
Second, you must not have spaces around =.
So it should be:
temp=${tab[$i+1]}
tab[$i+1]=${tab[$i]}
tab[$i]=$temp
I understand that you are working on a class assignment or such and are restricted in how you are allowed to solve the problem. For those not so restricted, here is a simple solution using standard unix tools:
#!/bin/bash
( IFS=$'\n'; echo "$*" ) | sort -n
Sample usage:
$ script.sh 3 5 6 2 1
1
2
3
5
6
Explanation:
( IFS=$'\n'; echo "$*" )
This causes the command line arguments to be printed, one per line. This is in a subshell so that the assignment to IFS does not affect the rest of the script.
sort -n
-n tells sort to apply a numeric sort.

Resources