Maximum element and its indices from an array in shell script - arrays

How can I find the maximum element and its index from an array in shell script. I have an array
a = [-2.2116565098 -2.1238242060 -2.1747941240 -2.3201010162 -2.3677779871 -1.8126464132 -2.1247209755 -2.1190930712 -2.3242384636 -2.1081702064];
Now, I want to find the maximum as well as its index in bash script. Is there a shortcut like in Matlab we have
[C, I] = max(a);
Also, also how can we have multi-dimensional array and get the index and value of minimum and maximum element.

$ x='-2.2116565098 -2.1238242060 -2.1747941240 -2.3201010162 -2.3677779871'
$ IC=(`tr ' ' '\n' <<<$x | cat -n | sort -k2,2nr | head -n1`)
$ I=${IC[0]} C=${IC[1]}
$ echo $I $C
2 -2.1238242060

Shell scripts in general do not support arrays at all, so what you are asking for is impossible. I am not aware of any shells that support multi-dimensional arrays, but some shells do provide minimal support for one dimensional arrays. Some of those shells probably provide convenient ways to perform the operations you need. To find the maximum value and the index in bash, which is one particular shell that does provide primitive support for arrays, you will need to loop over the array (as far as I know). However, bash does not provide good support for floating point values, so before you implement this, you should consider using a different language. Here is an example of one method:
idx=0
maxidx=0
max=${a[0]}
for v in ${a[#]}; do
expr $v \> $max > /dev/null && { maxidx=$idx; max=$v; }
: $((idx++))
done
There may be better techniques within bash for accessing the array, but it is generally a bad idea IMO to use shell specific constructs. If you are going to be using sh, even arrays really ought to be avoided, because not all shells support them. If you want to use a language feature of a non-standard shell, you might as well use perl, python, ruby, or your language of choice.

Related

Bash: Store sed result into array?

How to fix the following code so that it can store the result of sed, which will replace the _
with -?
My code:
names=()
for entry_ in $foo
do
names+=($entry_ | sed -e "s/_/-/g")
done
echo names
You don't need sed for this, you can use bash's built-in parameter expansion + substitution capability to replace all _ characters with -: ${var//_/-}. You can even use it to do this for the entire list of elements in a single operation, but how you do it depends on what the source variable, foo, actually is.
If foo is an array (the much better way to do things), you can combine [#] ("get me all elements of the array") with the substitution:
names=( "${foo[#]//_/-}" )
If foo is a plain string, and you need to use word splitting to break it into elements for the array, you can do essentially the same thing without the [#] ('cause it's not an array) or the double-quotes (which prevent word splitting):
names=( ${foo//_/-} )
Note: I recommend avoiding word splitting if possible -- it often does something close to what you want, but almost never exactly what you want.
P.s. I third the recommendation of shellcheck. Among other things, it'll flag anything involving word splitting as a probable mistake.
This should be enough to get you there.
names=()
names+=$(echo "hello_world" | sed -e "s/_/-/g")
echo $names
Note that you need $ before echoing your variable.
Also. Look into installing shellcheck for your code editor and it will help you catch sneaky bugs and build better shell programming practices.

how to enumerate multiple arrays using same base name

I am trying to create multiple arrays holding random lists of file names referencing the number of elements in another array. How can I append a $cntr var (beginning with cntr=0) to the end of the new array names so they are directly referenced with elements in other array?
Wow I hope that reads somewhat sensible. Here is what I got going on so far that I hope helps make better sense of what I mean:
function fGenRanList() {
cntr=0
while [[ "$cntr" -lt "${#mTypeAr[#]}" ]] ; do
n="${nAr[$cntr]}" ; echo "\$n: $n"
tracks${cntr}=() ; echo "\$tracks${cntr}: $tracks${cntr}"
while ((n > 0)) && IFS= read -rd $'\0' ; do
tracks${cntr}+=("$REPLY")
((n--))
done < <(sort -zuR <(find "${dirAr[$cntr]}" -type f \( -name '*.mp3' -o -name '*.ogg' \) -print0))
((cntr++))
done
}
error I get is:
/home/user/bin/ranSong_multDirs.sh: line 95: syntax error near unexpected token `"$REPLY"'
/home/user/bin/ranSong_multDirs.sh: line 95: ` tracks${cntr}+=("$REPLY")'
But I first commentted out the echo statements from the tracks${cntr}=() array initialization to get rid of a similar error, but unsure whether or not track${cntr} gets initialized in the first place.
By the end I should end up with as many track(n) arrays as there are elements in ${#mTypeAr[#]}, using the numeric var stored in array ${nAr[$cntr]} to determine how many elements each track array will contain.
Maybe I am making things more difficult than need be, trying to implement arrays into older scripts I have both in order to make them a little more efficient, but I guess am driven primarily to get a better handle on using BASH arrays to store vars for similar but multiple processes which I seem to do often in my scripts.
Change this line, which is not valid bash syntax,
tracks${cntr}+=("$REPLY")
to
declare "tracks${cntr}+=($REPLY)"
Rather than having a syntactic assignment, the declare command takes a string that *look*s like an assignment as an argument; that argument is processed by the shell first, so if cntr is currently 3 and $REPLY is foo, the actual assignment performed is
tracks3+=(foo)
The declare command gives you a level of indirection in making parameter assignments.

Devel::Size reported size of sub

I was just reading this article from Gabor Szabo and he pointed out that Devel::Size reports that a simple sub {} is reported as requiring 8,516 bytes of space. Is this reported size correct? If so, why does Perl need to allocate so much space for a single empty subroutine?
$ perl -MDevel::Size=total_size -E'
my $s = "x" x 100_000;
my $x = \$s;
my $y = \$s;
say total_size($x);
say total_size($y);
'
100048
100048
Does that mean that the size of $x and $y combined is 200KB? No. Same idea here.
It's not the size of the sub, but the size of the references, and everything it references, directly and indirectly.
$ perl -MDevel::Size=total_size -E'
sub f { } say total_size(\&f);
${"xxx"}=1; say total_size(\&f);
${"yyy"}=1; say total_size(\&f);
'
5847
5908
5969
As you can see, this is measuring more than just the sub. There appears to be a pointer to the sub's namespace.
Devel::Size doesn't have easily understandable rules for what to include when measuring the size of a a complex item like a subroutine.
Devel::SizeMe is an experimental fork of Devel::Size which uses reference counting to decide what to include, so the result is much more understandable. It also includes ways to visualize the internal structure of the data.
You can find more information of Devel::SizeMe, including links to slides and screencasts, here.

Bash - expanding variable nested in variable

Noble StackOverflow readers,
I have a comma seperated file, each line of which I am putting into an array.
Data looks as so...
25455410,GROU,AJAXa,GROU1435804437
25455410,AING,EXS3d,AING4746464646
25455413,TRAD,DLGl,TRAD7176202067
There are 103 lines and I am able to generate the 103 arrays without issue.
n=1; while read -r OrdLine; do
IFS=',' read -a OrdLineArr${n} <<< "$OrdLine"
let n++
done < $WkOrdsFile
HOWEVER, I can only access the arrays as so...
echo "${OrdLineArr3[0]} <---Gives 25455413
I cannot access it with the number 1-103 as a variable - for example the following doesn't work...
i=3
echo "${OrdLineArr${i}[0]}
That results in...
./script2.sh: line 24: ${OrdLineArr${i}[0]}: bad substitution
I think that the answer might involve 'eval' but I cannot seem to find a fitting example to borrow. If somebody can fix this then the above code makes for a very easy to handle 2d array replacement in bash!
Thanks so much for you help in advance!
Dan
You can use indirect expansion. For example, if $key is OrdLineArr4[7], then ${!key} (with an exclamation point) means ${OrdLineArr4[7]}. (See ยง3.5.3 "Shell Parameter Expansion" in the Bash Reference Manual, though admittedly that passage doesn't really explain how indirect expansion interacts with arrays.)
I'd recommend wrapping this in a function:
function OrdLineArr () {
local -i i="$1" # line number (1-103)
local -i j="$2" # field number (0-3)
local key="OrdLineArr$i[$j]"
echo "${!key}"
}
Then you can write:
echo "$(OrdLineArr 3 0)" # prints 25455413
i=3
echo "$(OrdLineArr $i 0)" # prints 25455413
This obviously isn't a total replacement for two-dimensional arrays, but it will accomplish what you need. Without using eval.
eval is usually a bad idea, but you can do it with:
eval echo "\${OrdLineArr$i[0]}"
I would store each line in an array, but split it on demand:
readarray OrdLineArr < $WkOrdsFile
...
OrdLine=${OrdLineArr[i]}
IFS=, read -a Ord <<< "$OrdLine"
However, bash isn't really equipped for data processing; it's designed to facilitate process and file management. You should consider using a different language.

Elegant use of arrays in ksh

I'm trying build an sort of property set in ksh.
Thought the easiest way to do so was using arrays but the syntax is killing me.
What I want is to
Build an arbitrary sized array in a config file with a name and a property.
Iterate for each item in that list and get that property.
I theory what I wish I could do is something like
MONITORINGSYS={
SYS1={NAME="GENERATOR" MONITORFUNC="getGeneratorStatus"}
SYS2={NAME="COOLER" MONITORFUNC="getCoolerStatus"}
}
Then later on, be able to do something like:
for CURSYS in $MONITORINGSYS
do
CSYSNAME=$CURSYS.NAME
CSYSFUNC=$CURSYS.MONITORFUNC
REPORT="$REPORT\n$CSYSNAME"
CSYSSTATUS=CSYSFUNC $(date)
REPORT="$REPORT\t$CSYSSTATUS"
done
echo $REPORT
Well, that's not real programming, but I guess you got the point..
How do I do that?
[EDIT]
I do not mean I want to use associative arrays. I only put this way to make my question more clear... I.e. It would not be a problem if the loop was something like:
for CURSYS in $MONITORINGSYS
do
CSYSNAME=${CURSYS[0]}
CSYSFUNC=${CURSYS[1]}
REPORT="$REPORT\n$CSYSNAME"
CSYSSTATUS=CSYSFUNC $(date)
REPORT="$REPORT\t$CSYSSTATUS"
done
echo $REPORT
Same applies to the config file.. I'm just looking for a syntax that makes it minimally readable.
cheers
Not exactly sure what you want... Kornshell can handle both associative and indexed arrays.
However, Kornshell arrays are one dimensional. It might be possible to use indirection to emulate a two dimensional array via the use of $() and eval. I did this a couple of times in the older Perl 4.x and Perl 3.x, but it's a pain. If you want multidimensional arrays, use Python or Perl.
The only thing is that you must declare arrays via the typedef command:
$ typeset -A foohash #foohash is an associative array
$ typeset -a foolist #foolist is an integer indexed array.
Maybe your script can look something like this
typeset -a sysname
typeset -a sysfunct
sysname[1] = "GENERATOR"
sysname[2] = "COOLER"
sysfunc[1] = "getGeneratorStatus"
sysfunc[2] = "getCoolerStatus"
for CURSYS in {1..2}
do
CSYSNAME="${sysname[$CURSYS]}"
CSYSFUNC="${sysfunc[$CURSYS]}"
REPORT="$REPORT\n$CSYSNAME"
CSYSSTATUS=$(eval "CSYSFUNC $(date)")
REPORT="$REPORT\t$CSYSSTATUS"
done
echo $REPORT
ksh93 now has compound variables which can contain a mixture of indexed and associative arrays. No need to declare it as ksh will work it out itself.
#!/bin/ksh
MONITORINGSYS=(
[SYS1]=(NAME="GENERATOR" MONITORFUNC="getGeneratorStatus")
[SYS2]=(NAME="COOLER" MONITORFUNC="getCoolerStatus")
)
echo MONITORING REPORT
echo "-----------------"
for sys in ${!MONITORINGSYS[*]}; do
echo "System: $sys"
echo "Name: ${MONITORINGSYS[$sys].NAME}"
echo "Generator: ${MONITORINGSYS[$sys].MONITORFUNC}"
echo
done
Output:
MONITORING REPORT
-----------------
System: SYS1
Name: GENERATOR
Generator: getGeneratorStatus
System: SYS2
Name: COOLER
Generator: getCoolerStatus

Resources