array+=value not work in ksh? - ksh

I read somewhere that ksh's array supports += to append new elements, but I tried it and it doesn't work:
[ksh] # arr=(a b c d)
[ksh] # arr+=e
[ksh] # echo ${arr[*]}
ae b c d
[ksh] #
Why does arr[0] becomes ae?

To add an element to the array, it should be like this:
arr+=(e)
By doing arr+=e , it will add to the 1st element of the array. Its because just the name arr points to the 1st element of the array itself:
$ arr=(a b c d)
$ echo ${arr[0]}
a
$ echo $arr
a

It's arr+=(e). Any simple assignment that doesn't specify an index always refers to the zeroth element.
Note that this is a trivial case and things can get more complicated. += has different behavior for both simple and compound assignment depending on context, and also differs between bash, ksh93, and zsh, so it's easy to become confused.
http://wiki.bash-hackers.org/syntax/arrays#storing_values

Related

golang sort.Strings vs unix sort, how do I sort according to a given collation?

I was puzzled for a while because the two methods of sorting behave differently, or at least in my environment. After a while, I realized it's because of different sorting rules.
A quick experiment illustrates my point:
$ foo() { echo "o'4" "o'neil" o-ciclo-music ó-do-forró o-1 o-2 o-3 "o'" "o'1" "o'2" aaa b bb; }
$ foo | s2n | LC_ALL=C sort > /tmp/s.c
$ foo | s2n | LC_ALL="en_US.UTF-8" sort > /tmp/s.u
$ paste /tmp/s.{c,u} | awk '{ printf "%16s %s\n", $1 , $2 ; }'
aaa aaa
b b
bb bb
o' o'
o'1 o'1
o'2 o-1
o'4 o'2
o'neil o-2
o-1 o-3
o-2 o'4
o-3 o-ciclo-music
o-ciclo-music ó-do-forró
ó-do-forró o'neil
The left column is sorted according to ascii-rules while the right column is sorted according to utf8 rules. The golang sort.Strings() behaves according to ascii rules. That function is based on the < and = operators and they in turn operates on integers. So the words are being sorted according to the ascii ordinal values of their letters.
It it possible to influence how sort.Strings() behaves based on some environment variable? I have tried with the LC_ALL variables, but the 'a' < 'b' behavior in Golang is not influenced by that. This is probably for the better.
I am wondering which package provides a function with the same signature (and return type)
as strings.Compare(), but will heed some arbitrary collation rule-set (or at least unicode).
I want a function that indicates that o'neil should come after ó-do-forró.
foobar.SetCollation(unicode)
foobar.Compare(`o'neil`, `ó-do-forró`) -> 1
What package am I looking for?
import "golang.org/x/text/collate"
(...)
cull := collate.New(language.English,
collate.IgnoreCase, collate.IgnoreDiacritics, collate.IgnoreWidth,
collate.Loose,
collate.Force,
collate.Numeric)
println(cull.CompareString( "o'neil", "ó-do-forró", )) // prints 1
println(cull.CompareString( "b", "a", )) // prints 1

How can I print the name of the antepenultimate folder of a directory? bash

I am trying to print the name of the antepenultimate folder for multiple directories within a for loop.
I would like to know if there is a way to get this name easily.
Example:
array=(/ a / b / c / d / e / f / g / h / i / j)
The folder that I'm trying to print is h (The position of folder h is always the same but the name is different.
Subtract 5 from the length of the array to get the array index.
echo "${array[${#array[#]}-5]}"
Or in modern versions of bash simply:
echo "${array[-5]}"
$(( )) is the syntax to enter a math context.
echo "${array[$((${#array[#]} - 5))]}"
Assuming you really have an array with single letter folder names and the path delimiters to represent a path. (Which is the wrong way btw to do this)
With "modern bash" (ie, version 4.1 released in 2009 or later versions) you can use negative indexing to return from the end:
$ array=(/ a / b / c / d / e / f / g / h / i / j)
$ echo "${array[-5]}"
h
(That also works under zsh where calculating the length and subtract does not since zsh has base 1 arrays and Bash has base 0 arrays...)
However, most paths are strings and it is usually easier to manipulate the string representing a path. Your array method breaks if the individual folder names are not single letters.
Suppose you have a path represented as:
$ path="/a/b/c/d/e/f/g/h/i/j"
You can use Bash string manipulation functions to retrieve that position as so:
$ s1="${path%/*/*}" # remove the last two directories
$ echo "${s1##*/}" # remove all directories up to the last
h
The advantage of the string method is the / acts as a true delimiter between folder names of arbitrary length.
Consider:
$ path="/Foulder_a/sub_b/c/AND_d/e/this_is_f/and_this_is_g/THIS_ONE_I_WANT_h/penultimate/last"
$ s1="${path%/*/*}"
$ echo "${s1##*/}"
THIS_ONE_I_WANT_h
Try and do that with the array method...
From comments:
$ path="/a/sub_b/c/AND_d/e/the_f/this_is_g/THIS_ONE_I_WANT_h/penultimate/last"
$ s1="${path%/*/*}"
$ name="${s1##*/}"
$ new_path="${s1}/${name}.txt"
$ echo "$new_path"
/a/sub_b/c/AND_d/e/the_f/this_is_g/THIS_ONE_I_WANT_h/THIS_ONE_I_WANT_h.txt

Splitting a list in bash

I have this script:
#!/bin/bash
list="a b c d"
for item in ${list[#]}; do
echo "${item}"
done
When I run it this is the output:
a
b
c
d
This is exactly what I want. However, shellcheck hates this and throws an error:
for item in ${list[#]}; do
^-- SC2068: Double quote array expansions to avoid re-splitting elements.
But, when I double quote the variable the output of the script changes to this:
a b c d
Which is not what I want.
Is shellcheck right and should I modify the way I try to extract the items from the variable, but how? Or should I just tell shellcheck to ignore this?
This is not an array:
list="a b c d"
You're just assigning list to a string of length 7.
To make it a real array:
list=(a b c d)
Then with for item in "${list[#]}", you get the correct result.
For your updated question, you should just use $list instead of ${list[#]}, because list isn't an array.
I was getting this error when using code below:
redhatCatalogs=("certified-operators" "redhat-marketplace")
for catalog in ${redhatCatalogs[#]}; do
...
Notice I am missing the quotes, after adding the quotes, problem was solved:
redhatCatalogs=("certified-operators" "redhat-marketplace")
for catalog in "${redhatCatalogs[#]}"; do
...
So in conclusion, consider the quotes as well!: "${redhatCatalogs[#]}"

shell scripting passing 2D arrays to function

how to pass 2d array to function in shell script ?
i need to pass matrix to function but it do not work
tr(){
matrix="$3"
num_rows="$1"
num_columns="$2"
f1="%$((${#num_rows}+1))s"
f2=" %9s"
for ((i=1;i<=num_rows;i++)) do
for ((j=1;j<=num_columns;j++)) do
echo -ne "${matrix[$i,$j]}\t"
done
echo -e "\n"
done
tr $rows $columns $x
Use an associative array:
declare -A matrix
Then things like matrix[6,7]=42 will work because "6,7" ist just a string, and associative arrays accept strings as indices. You might as well write things like
matrix[one,two]=three
matrix[yet,another,dimension]="Perry Rhodan"
You can just write any string between [ and ]. Here is a complete example for how to use it.
#!/bin/bash
#
# Example for a function that accepts the name of an associative array ...
# ... and does some work on the array entries, e.g. compute their sum
# We assume that all *values* of the array are integers - no error check
sum() {
local s=0 # we don't want to interfere with any other s
declare -n local var="$1" # now var references the variable named in $1
for value in "${var[#]}" # value runs through all values of the array
do
let s+="$value"
done
echo sum is $s
}
declare -A m # now m is an associative array, accepting any kind of index
m[0,0]=4 # this looks like 2-dimensional indexing, but is is not
m[2,3]=5 # m will accept any reasonable string as an array index
m[678]=6 # m does not care about the number of commas between numbers
m[foo]=7 # m does not even care about the indices being numbers at all
sum m
As you see, the matrix m not really has 2 dimensions. It just takes any string as an index, as long as it does not contains certain shell syntax characters, and comma is allowed in the string.
Please note the reference declare -n ... - this allows simple access to the matrix from within the function and, most important, without knowing the name of the matrix. Thus you can call that function for several matrices with different names.
The keyword local is important. It means that, upon return, var is unset automatically. Otherwise you will have a reference "var" to an associative array. If you ever want to use var later, it will be hard to use it because you cannot use it as anything else but an associative array. And if you try to get rid of it by "unset var", bash will kindly remember that var refers to m, and delete your matrix m instead. In general, make variables in functions be local wherever possible. It even allows me to re-use a name. For example, using "s" as a variable name inside a function may appear dangerous because it might change the value of a global variable "s". But it doesn't - by declaring it local, the function has its own private variable s, and any s that might already exist is untouched.
Just as a demonstration: If you want to see the array indices in the loop, do this:
sum() {
local s=0 # we don't want to interfere with any other s
declare -n local var="$1" # now var references the variable named in $1
for i in "${!var[#]}" # !var means that we run through all indices
do # we really need a reference here because ...
let s+=${var["$i"]} # ... indirections like ${!$1[$i]} won't work
done
echo sum is $s
}

Change a referenced variable in BASH

I am intending to change a global variable inside a function in BASH, however I don't get a clue about how to do it. This is my code:
CANDIDATES[5]="1 2 3 4 5 6"
random_mutate()
{
a=$1 #assign name of input variable to "a"
insides=${!a} #See input variable value
RNDM_PARAM=`echo $[ 1 + $[ RANDOM % 5 ]]` #change random position in input variable
NEW_PAR=99 #value to substitute
ARR=($insides) #Convert string to array
ARR[$RNDM_PARAM]=$NEW_PAR #Change the random position
NEW_GUY=$( IFS=$' '; echo "${ARR[*]}" ) #Convert array once more to string
echo "$NEW_GUY"
### NOW, How to assign NEW_GUY TO CANDIDATES[5]?
}
random_mutate CANDIDATES[5]
I would like to be able to assign NEW_GUY to the variable referenced by $1 or to another variable that would be pointed by $2 (not incuded in the code). I don't want to do the direct assignation in the code as I intend to use the function for multiple possible inputs (in fact, the assignation NEW_PAR=99 is quite more complicated in my original code as it implies the selection of a number depending the position in a range of random values using an R function, but for the sake of simplicity I included it this way).
Hopefully this is clear enough. Please let me know if you need further information.
Thank you,
Libertad
You can use eval:
eval "$a=\$NEW_GUY"
Be careful and only use it if the value of $a is safe (imagine what happens if $a is set to rm -rf / ; a).

Resources