Unix Shell scripting (KSH) - shell

Can we create dynamic arrays in Ksh?
like, can we create array_$i[] arrays where i ranges from 1 to 5? i.e., array_1[], array_2[],array_3[], array_4[] and array_5[]
so that I can iterate through array_$i.
Is that possible in ksh?

You can do it with eval
eval array_$i'[$j]=$val'
eval 'echo $array_'$i

In ksh93, you can create compound variables. These are really hashes, not arrays, but can be used as such, when using numeric indexes.
$ $ echo $KSH_VERSION
Version JM 93u 2011-02-08
$ a[1][1]=foo
$ a[3][1]=bar
$ print -C a
([1]=([1]=foo) [3]=([1]=bar) )
$ print -v a
(
[1]=(
[1]=foo
)
[3]=(
[1]=bar
)
)
$ print ${a[1][1]}
foo
$ print ${a[3][1]}
bar

To develop more Bamar's answer here is an example of how those "arrays" could be used in ksh:
#!/usr/bin/ksh
#--------------------------------------
# populate arrays
for i in 1 2 3 4 5 ; do # here we will define/use 5 arrays
count=$RANDOM%10 # random array size up to 10 elements
while [[ $count -gt 0 ]]; do
let count=$count-1
eval my_arrays_${i}[$count]=$RANDOM # assigning some random numbers to the array elements
done
done
#--------------------------------------
# display arrays content
for i in 1 2 3 4 5 ; do
idx=0
arr_name=my_arrays_${i}
arr_name_cnt="\${#$arr_name[*]}"
eval count=$arr_name_cnt # getting the array count of elements
echo "Array $arr_name has $count elements"
while [[ $idx -lt $count ]]; do # loop over array elements
arr_elem="\${${arr_name}[$idx]}"
eval arr_elem=$arr_elem # getting the actual value of the array element
echo "Element $idx: < $arr_elem > "
let idx+=1
done
done

Related

Using a positional parameter as the name of the array I want to loop over

I have been looking at other similar questions but didn't find the proper answer. $1 is the name of the array i want to loop over.
#!/bin/bash
for i in ${"$1"[#]}
do
echo "$i"
done
If I don't misunderstand your not very well written question, you are looking for bash indirection, which is available since bash, version 2.
Below a minimal example
#! /bin/bash
foo=( 1 2 3 4 )
bar=( 5 6 7 8 )
if [[ ( "$1" -ne foo && "$1" -ne bar ) ]];
then
echo "Usage: $0 <foo|bar>"
exit 1
fi
ambito="$1"[#] # here what you are looking for
for i in "${!ambito}"; # here the indirection
do
echo -n "$i "
done
echo ""
And you can call this indirection.sh scrit as:
$ ./indirection.sh foo
1 2 3 4
or
$ ./indirection.sh bar
5 6 7 8
This being said, using indirection may cause confusion. In most cases it could be replaced by associative arrays.
$1 is the first command line argument, not all of them. Probably what you are looking for is $#.
And to create an array holding them, something like:
ambito=($#)

Setting changing env variable from associative array [duplicate]

I'm attempting to read an input file line by line which contains fields delimited by periods.
I want to put them into an array of arrays so I can loop through them later on. The input appears to be ok, but 'pushing' that onto the array (inData) doesn't appear to be working.
The code goes :
Input file:
GSDB.GOSALESDW_DIST_INVENTORY_FACT.MONTH_KEY
GSDB.GOSALESDW_DIST_INVENTORY_FACT.ORGANIZATION_KEY
infile=${1}
OIFS=$IFS
IFS=":"
cat ${infile} | while read line
do
line=${line//\./:}
inarray=(${line})
# echo ${inarray[#]}
# echo ${#inarray[#]}
# echo ${inarray[0]}
# echo ${inarray[1]}
# echo ${inarray[2]}
inData=("${inData[#]}" "${inarray[#]}")
done
IFS=$OIFS
echo ${#inData[#]}
for ((i = 0; i < ${#inData[#]}; i++))
do
echo $i
for ((j = 0; j < ${#inData[$i][#]}; j++))
do
echo ${inData[$i][$j]}
done
done
Field nest box in bash but it can not circumvent see the example.
#!/bin/bash
# requires bash 4 or later; on macOS, /bin/bash is version 3.x,
# so need to install bash 4 or 5 using e.g. https://brew.sh
declare -a pages
pages[0]='domain.de;de;https'
pages[1]='domain.fr;fr;http'
for page in "${pages[#]}"
do
# turn e.g. 'domain.de;de;https' into
# array ['domain.de', 'de', 'https']
IFS=";" read -r -a arr <<< "${page}"
site="${arr[0]}"
lang="${arr[1]}"
prot="${arr[2]}"
echo "site : ${site}"
echo "lang : ${lang}"
echo "prot : ${prot}"
echo
done
Bash has no support for multidimensional arrays. Try
array=(a b c d)
echo ${array[1]}
echo ${array[1][3]}
echo ${array[1]exit}
For tricks how to simulate them, see Advanced Bash Scripting Guide.
Knowing that you can split string into "array". You could creat a list of lists. Like for example a list of databases in DB servers.
dbServersList=('db001:app001,app002,app003' 'db002:app004,app005' 'dbcentral:central')
# Loop over DB servers
for someDbServer in ${dbServersList[#]}
do
# delete previous array/list (this is crucial!)
unset dbNamesList
# split sub-list if available
if [[ $someDbServer == *":"* ]]
then
# split server name from sub-list
tmpServerArray=(${someDbServer//:/ })
someDbServer=${tmpServerArray[0]}
dbNamesList=${tmpServerArray[1]}
# make array from simple string
dbNamesList=(${dbNamesList//,/ })
fi
# Info
echo -e "\n----\n$someDbServer\n--"
# Loop over databases
for someDB in ${dbNamesList[#]}
do
echo $someDB
done
done
Output of above would be:
----
db001
--
app001
app002
app003
----
db002
--
app004
app005
----
dbcentral
--
central
I struggled with this but found an uncomfortable compromise. In general, when faced with a problem whose solution involves using data structures in Bash, you should switch to another language like Python. Ignoring that advice and moving right along:
My use cases usually involve lists of lists (or arrays of arrays) and looping over them. You usually don't want to nest much deeper than that. Also, most of the arrays are strings that may or may not contain spaces, but usually don't contain special characters. This allows me to use not-to-confusing syntax to express the outer array and then use normal bash processing on the strings to get a second list or array. You will need to pay attention to your IFS delimiter, obvi.
Thus, associative arrays can give me a way to create a list of lists like:
declare -A JOB_LIST=(
[job1] = "a set of arguments"
[job2] = "another different list"
...
)
This allows you to iterate over both arrays like:
for job in "${!JOB_LIST[#]}"; do
/bin/jobrun ${job[#]}
done
Ah, except that the output of the keys list (using the magical ${!...}) means that you will not traverse your list in order. Therefore, one more necessary hack is to sort the order of the keys, if that is important to you. The sort order is up to you; I find it convenient to use alphanumerical sorting and resorting to aajob1 bbjob3 ccjob6 is perfectly acceptable.
Therefore
declare -A JOB_LIST=(
[aajob1] = "a set of arguments"
[bbjob2] = "another different list"
...
)
sorted=($(printf '%s\n' "${!JOB_LIST[#]}"| /bin/sort))
for job in "${sorted[#]}"; do
for args in "${job[#]}"; do
echo "Do something with ${arg} in ${job}"
done
done
I use Associative Arrays and use :: in the key to denote depth.
The :: can also be used to embed attributes, but that is another subject,...
declare -A __myArrayOfArray=([Array1::Var1]="Assignment" [Array2::Var1]="Assignment")
An Array under Array1
__myArrayOfArray[Array1::SubArray1::Var1]="Assignment"
The entries in any array can be retrieved (in order ...) by ...
local __sortedKeys=`echo ${!__myArrayOfArray[#]} | xargs -n1 | sort -u | xargs`
for __key in ${__sortedKeys}; do
#
# show all properties in the Subordinate Profile "Array1::SubArray1::"
if [[ ${__key} =~ ^Array1::SubArray1:: ]]; then
__property=${__key##Array1::SubArray1::}
if [[ ${__property} =~ :: ]]; then
echo "Property ${__property%%:*} is a Subordinate array"
else
echo "Property ${__property} is set to: ${__myArrayOfArray[${__key}]}"
fi
fi
done
THE list of subordinate "Profiles" can be derived by:
declare -A __subordinateProfiles=()
local __profile
local __key
for __key in "${!__myArrayOfArray[#]}"; do
if [[ $__key =~ :: ]]; then
local __property=${__key##*:}
__profile=${__key%%:*}
__subordinateProfiles[${__profile}]=1
fi
done
A bash array of arrays is possible, if you convert and store each array as a string using declare -p (see my function stringify). This will properly handle spaces and any other problem characters in your arrays. When you extract an array, use function unstringify to rebuild the array. This script demonstrates an array of arrays:
#!/bin/bash
# BASH array of arrays demo
# Convert an array to a string that can be used to reform
# the array as a new variable. This allows functions to
# return arrays as strings. Works for arrays and associative
# arrays. Spaces and odd characters are all handled by bash
# declare.
# Usage: stringify variableName
# variableName - Name of the array variable e.g. "myArray",
# NOT the array contents.
# Returns (prints) the stringified version of the array.
# Examples. Use declare to make an array:
# declare -a myArray=( "O'Neal, Dan" "Kim, Mary Ann" )
# (Or to make a local variable replace declare with local.)
# Stringify myArray:
# stringifiedArray="$(stringify myArray)"
# Reform the array with any name like reformedArray:
# eval "$(unstringify reformedArray "$stringifiedArray")"
# To stringify an argument list "$#", first create the array
# with a name: declare -a myArgs=( "$#" )
stringify() {
declare -p $1
}
# Reform an array from a stringified array. Actually this prints
# the declare command to form the new array. You need to call
# eval with the result to make the array.
# Usage: eval "$(unstringify newArrayName stringifiedArray [local])"
# Adding the optional "local" will create a local variable
# (uses local instead of declare).
# Example to make array variable named reformedArray from
# stringifiedArray:
# eval "$(unstringify reformedArray "$stringifiedArray")"
unstringify() {
local cmd="declare"
[ -n "$3" ] && cmd="$3"
# This RE pattern extracts 2 things:
# 1: the array type, should be "-a" or "-A"
# 2: stringified contents of the array
# and skips "declare" and the original variable name.
local declareRE='^declare ([^ ]+) [^=]+=(.*)$'
if [[ "$2" =~ $declareRE ]]
then
printf '%s %s %s=%s\n' "$cmd" "${BASH_REMATCH[1]}" "$1" "${BASH_REMATCH[2]}"
else
echo "*** unstringify failed, invalid stringified array:" 1>&2
printf '%s\n' "$2" 1>&2
return 1
fi
}
# array of arrays demo
declare -a array # the array holding the arrays
declare -a row1=( "this is" "row 1" )
declare -a row2=( "row 2" "has problem chars" '!##$%^*(*()-_=+[{]}"|\:;,.<.>?/' )
declare -a row3=( "$#" ) # row3 is the arguments to the script
# Fill the array with each row converted to a string.
# stringify needs the NAME OF THE VARIABLE, not the variable itself
array[0]="$(stringify row1)"
array[1]="$(stringify row2)"
array[2]="$(stringify row3)"
# Print array contents
for row in "${array[#]}"
do
echo "Expanding stringified row: $row"
# Reform the row as the array thisRow
eval "$(unstringify thisRow "$row")"
echo "Row values:"
for val in "${thisRow[#]}"
do
echo " '$val'"
done
done
You could make use of (de)referencing arrays like in this script:
#!/bin/bash
OFS=$IFS # store field separator
IFS="${2: }" # define field separator
file=$1 # input file name
unset a # reference to line array
unset i j # index
unset m n # dimension
### input
i=0
while read line
do
a=A$i
unset $a
declare -a $a='($line)'
i=$((i+1))
done < $file
# store number of lines
m=$i
### output
for ((i=0; i < $m; i++))
do
a=A$i
# get line size
# double escape '\\' for sub shell '``' and 'echo'
n=`eval echo \\${#$a[#]}`
for (( j = 0; j < $n; j++))
do
# get field value
f=`eval echo \\${$a[$j]}`
# do something
echo "line $((i+1)) field $((j+1)) = '$f'"
done
done
IFS=$OFS
Credit to https://unix.stackexchange.com/questions/199348/dynamically-create-array-in-bash-with-variables-as-array-name

The meaning of ${TF_CFLAGS[#]} [duplicate]

How do I create an array in unix shell scripting?
The following code creates and prints an array of strings in shell:
#!/bin/bash
array=("A" "B" "ElementC" "ElementE")
for element in "${array[#]}"
do
echo "$element"
done
echo
echo "Number of elements: ${#array[#]}"
echo
echo "${array[#]}"
Result:
A
B
ElementC
ElementE
Number of elements: 4
A B ElementC ElementE
in bash, you create array like this
arr=(one two three)
to call the elements
$ echo "${arr[0]}"
one
$ echo "${arr[2]}"
three
to ask for user input, you can use read
read -p "Enter your choice: " choice
Bourne shell doesn't support arrays. However, there are two ways to handle the issue.
Use positional shell parameters $1, $2, etc.:
$ set one two three
$ echo $*
one two three
$ echo $#
3
$ echo $2
two
Use variable evaluations:
$ n=1 ; eval a$n="one"
$ n=2 ; eval a$n="two"
$ n=3 ; eval a$n="three"
$ n=2
$ eval echo \$a$n
two
#!/bin/bash
# define a array, space to separate every item
foo=(foo1 foo2)
# access
echo "${foo[1]}"
# add or changes
foo[0]=bar
foo[2]=cat
foo[1000]=also_OK
You can read the ABS "Advanced Bash-Scripting Guide"
The Bourne shell and C shell don't have arrays, IIRC.
In addition to what others have said, in Bash you can get the number of elements in an array as follows:
elements=${#arrayname[#]}
and do slice-style operations:
arrayname=(apple banana cherry)
echo ${arrayname[#]:1} # yields "banana cherry"
echo ${arrayname[#]: -1} # yields "cherry"
echo ${arrayname[${#arrayname[#]}-1]} # yields "cherry"
echo ${arrayname[#]:0:2} # yields "apple banana"
echo ${arrayname[#]:1:1} # yields "banana"
Try this :
echo "Find the Largest Number and Smallest Number of a given number"
echo "---------------------------------------------------------------------------------"
echo "Enter the number"
read n
i=0
while [ $n -gt 0 ] #For Seperating digits and Stored into array "x"
do
x[$i]=`expr $n % 10`
n=`expr $n / 10`
i=`expr $i + 1`
done
echo "Array values ${x[#]}" # For displaying array elements
len=${#x[*]} # it returns the array length
for (( i=0; i<len; i++ )) # For Sorting array elements using Bubble sort
do
for (( j=i+1; j<len; j++ ))
do
if [ `echo "${x[$i]} > ${x[$j]}"|bc` ]
then
t=${x[$i]}
t=${x[$i]}
x[$i]=${x[$j]}
x[$j]=$t
fi
done
done
echo "Array values ${x[*]}" # Displaying of Sorted Array
for (( i=len-1; i>=0; i-- )) # Form largest number
do
a=`echo $a \* 10 + ${x[$i]}|bc`
done
echo "Largest Number is : $a"
l=$a #Largest number
s=0
while [ $a -gt 0 ] # Reversing of number, We get Smallest number
do
r=`expr $a % 10`
s=`echo "$s * 10 + $r"|bc`
a=`expr $a / 10`
done
echo "Smallest Number is : $s" #Smallest Number
echo "Difference between Largest number and Smallest number"
echo "=========================================="
Diff=`expr $l - $s`
echo "Result is : $Diff"
echo "If you try it, We can get it"
Your question asks about "unix shell scripting", but is tagged bash. Those are two different answers.
The POSIX specification for shells does not have anything to say about arrays, as the original Bourne shell did not support them. Even today, on FreeBSD, Ubuntu Linux, and many other systems, /bin/sh does not have array support. So if you want your script to work in different Bourne-compatible shells, you shouldn't use them. Alternatively, if you are assuming a specific shell, then be sure to put its full name in the shebang line, e.g. #!/usr/bin/env bash.
If you are using bash or zsh, or a modern version of ksh, you can create an array like this:
myArray=(first "second element" 3rd)
and access elements like this
$ echo "${myArray[1]}" # for bash/ksh; for zsh, echo $myArray[2]
second element
You can get all the elements via "${myArray[#]}". You can use the slice notation ${array[#]:start:length} to restrict the portion of the array referenced, e.g. "${myArray[#]:1}" to leave off the first element.
The length of the array is ${#myArray[#]}. You can get a new array containing all the indexes from an existing array with "${!myArray[#]}".
Older versions of ksh before ksh93 also had arrays, but not the parenthesis-based notation, nor did they support slicing. You could create an array like this, though:
set -A myArray -- first "second element" 3rd
You can try of the following type :
#!/bin/bash
declare -a arr
i=0
j=0
for dir in $(find /home/rmajeti/programs -type d)
do
arr[i]=$dir
i=$((i+1))
done
while [ $j -lt $i ]
do
echo ${arr[$j]}
j=$((j+1))
done
An array can be loaded in twoways.
set -A TEST_ARRAY alpha beta gamma
or
X=0 # Initialize counter to zero.
-- Load the array with the strings alpha, beta, and gamma
for ELEMENT in alpha gamma beta
do
TEST_ARRAY[$X]=$ELEMENT
((X = X + 1))
done
Also, I think below information may help:
The shell supports one-dimensional arrays. The maximum number of array
elements is 1,024. When an array is defined, it is automatically
dimensioned to 1,024 elements. A one-dimensional array contains a
sequence of array elements, which are like the boxcars connected
together on a train track.
In case you want to access the array:
echo ${MY_ARRAY[2] # Show the third array element
gamma
echo ${MY_ARRAY[*] # Show all array elements
- alpha beta gamma
echo ${MY_ARRAY[#] # Show all array elements
- alpha beta gamma
echo ${#MY_ARRAY[*]} # Show the total number of array elements
- 3
echo ${#MY_ARRAY[#]} # Show the total number of array elements
- 3
echo ${MY_ARRAY} # Show array element 0 (the first element)
- alpha
If you want a key value store with support for spaces use the -A parameter:
declare -A programCollection
programCollection["xwininfo"]="to aquire information about the target window."
for program in ${!programCollection[#]}
do
echo "The program ${program} is used ${programCollection[${program}]}"
done
http://linux.die.net/man/1/bash "Associative arrays are created using declare -A name. "
There are multiple ways to create an array in shell.
ARR[0]="ABC"
ARR[1]="BCD"
echo ${ARR[*]}
${ARR[*]} prints all elements in the array.
Second way is:
ARR=("A" "B" "C" "D" 5 7 "J")
echo ${#ARR[#]}
echo ${ARR[0]}
${#ARR[#]} is used to count length of the array.
To read the values from keybord and insert element into array
# enter 0 when exit the insert element
echo "Enter the numbers"
read n
while [ $n -ne 0 ]
do
x[$i]=`expr $n`
read n
let i++
done
#display the all array elements
echo "Array values ${x[#]}"
echo "Array values ${x[*]}"
# To find the array length
length=${#x[*]}
echo $length
A Simple way :
arr=("sharlock" "bomkesh" "feluda" ) ##declare array
len=${#arr[*]} #determine length of array
# iterate with for loop
for (( i=0; i<len; i++ ))
do
echo ${arr[$i]}
done
In ksh you do it:
set -A array element1 element2 elementn
# view the first element
echo ${array[0]}
# Amount elements (You have to substitute 1)
echo ${#array[*]}
# show last element
echo ${array[ $(( ${#array[*]} - 1 )) ]}

How can I highlight given values in a generated numeric sequence?

I often receive unordered lists of document IDs. I can sort and print them easy enough, but I'd like to print a line for each available document and show an asterisk (or anything really, just to highlight) next to all values in the given list.
Such as ...
$ ./t.sh "1,4,3" 5
1*
2
3*
4*
5
$
The first parameter is the unordered list, and the second is the total number of documents.
If by "available document" you mean an "existing file on disk", then assuming you have 5 total files, and you are checking to see if you have 1, 4 and 3. The following script will produce sorted output.
#!/bin/bash
#Store the original IFS
ORGIFS=$IFS
#Now Set the Internal File Separater to a comma
IFS=","
###Identify which elements of the array we do have and store the results
### in a separate array
#Begin a loop to process each array element
for X in ${1} ; do
if [[ -f ${X} ]] ; then
vHAVE[$X]=YES
fi
done
#Now restore IFS
IFS=$ORGIFS
#Process the sequence of documents, starting at 1 and ending at $2.
for Y in $(seq 1 1 $2) ; do
#Check if the sequence exists in our inventoried array and mark accordingly.
if [[ ${vHAVE[$Y]} == YES ]] ; then
echo "$Y*"
else
echo "$Y"
fi
done
Returns the result:
rtcg#testserver:/temp/test# ls
rtcg#testserver:/temp/test# touch 1 3 4
rtcg#testserver:/temp/test# /usr/local/bin/t "1,4,3" 5
1*
2
3*
4*
5
The following code works for me on your example.
Generate a sequence of the length given by the user
Split the first argument of your script (it will gives you an array A for example)
Use the function contains to check if one element from A is in the sequence generated by the step one
I don't check the arguments length and you should do that to have a more proper script.
#!/bin/bash
function contains() {
local n=$#
local value=${!n}
for ((i=1;i < $#;i++)) {
if [ "${!i}" == "${value}" ]; then
echo "y"
return 0
fi
}
echo "n"
return 1
}
IFS=', ' read -a array <<< $1
for i in $(seq $2); do
if [ $(contains "${array[#]}" "${i}") == "y" ]; then
echo "${i}*"
else
echo "${i}"
fi
done
You can use parameter substitution to build an extended pattern that can be used to match document numbers to the list of documents to mark.
#!/bin/bash
# 1,4,3 -> 1|4|3
to_mark=${1//,/|}
for(( doc=1; doc <= $2; doc++)); do
# #(1|4|3) matches 1, 4 or 3
printf "%s%s\n" "$doc" "$( [[ $doc = #($to_mark) ]] && printf "*" )"
done

Parameter input works, but pipe doesn't

I tried to create a shell script, which sum the given numbers. If there is no given parameter, then it tries to read the pipe output, but I get an error.
#!/bin/sh
sum=0
if [ $# -eq 0 ]
then
while read data
do
sum=`expr $sum + $data`
done
else
for (( i = 1 ; i <= $#; i++ ))
do
sum=`expr $sum + ${!i}`
done
fi
echo $sum
This works: sum 10 12 13
But this one doesn't: echo 10 12 13| sum
Thanks in advance,
Here you go (assuming bash, not sh):
#!/bin/bash
sum=0
if (( $# == 0 )); then
# Read line by line
# But each line might consist of separate numbers to be added
# So read each line as an array!
while read -a data; do
# Now data is an array... but if empty, continue
(( ${#data[#]} )) || continue
# Convert this array into a string s, with elements separated by a +
printf -v s "%s+" ${data[#]}
# Append 0 to s (observe that s ended with a +)
s="${s}0"
# Add these numbers to sum
(( sum += s ))
done
else
# If elements come from argument line, do the same!
printf -v s "%s+" $#
# Append 0 to s (observe that s ended with a +)
s="${s}0"
# Add these numbers to obtain sum
(( sum = s ))
fi
echo $sum
You can invoke it thus:
$ echo 10 12 13 | ./sum
35
$ ./sum 10 12 13
35
$ # With several lines and possibly empty lines:
$ { echo 10 12 13; echo; echo 42 22; } | ./sum
99
Hope this helps!
Edit. You might also be interested in learning cool stuff about IFS. I've noticed that people tend to confuse # and * in bash. If you don't know what I'm talking about, then you should use # instead of *, also for array subscripts! In the bash manual, you'll find that when double quoted, $* (or ${array[*]}) expands to all the elements of the array separated by the value of the IFS. This can be useful in our case:
#!/bin/bash
sum=0
if (( $# == 0 )); then
# Read line by line
# But each line might consist of separate numbers to be added
# So read each line as an array!
while read -a data; do
# Now data is an array... but if empty, continue
(( ${#data[#]} )) || continue
# Setting IFS=+ (just for the sum) will yield exactly what I want!
IFS=+ sum=$(( sum + ${data[*]} ))
done
else
# If elements come from argument line, do the same!
# Setting IFS=+ (just for the sum) will yield exactly what I want!
IFS=+ sum=$(( $* ))
fi
echo $sum
Gniourf now exits from teacher mode. :-)

Resources