I was trying to emulate the C enum semantics with arrays but without much sucess, basically i want to be able to iterate through a set of items and also declare a variable just by using an identifier like this:
$ bash -version
GNU bash, versión 4.1.10(4)-release (i686-pc-cygwin)
bad attempt:
#!/bin/bash
STATES=(INITIAL DEFAULT_CS_SETUP CREATED_CS CHECKED_OUT_DIR MKELEMENT_FILE\
CREATED_BRANCH CHECKED_IN_DIR COMPLETE)
tam=${#STATES[#]}
dereference()
{
tam=${#STATES[#]}
for ((j=0; j < $tam; j++)); do
if [[ "$state" == ${STATES[j]} ]];then
echo $j
break
fi
done
}
echo get the INITIAL state
state=INITIAL
echo ${STATES[`dereference`]}
echo get the next state from CREATED_CS
state=CREATED_CS
echo ${STATES[`dereference`+1]}
echo list elements from CREATED_CS to the end
state=CREATED_CS
for ((i=`dereference`; i < $tam; i++)); do
echo ${STATES[$i]}
done
echo list elements from CREATED_CS to CREATED_BRANCH is really awkward
state=CREATED_BRANCH
tmp_ind=`dereference`
state=CREATED_CS
for ((i=`dereference`; i <= $tmp_ind; i++)); do
echo ${STATES[$i]}
done
output:
get the INITIAL state
INITIAL
get the next state from CREATED_CS
CHECKED_OUT_DIR
list elements from CREATED_CS to the end
CREATED_CS
CHECKED_OUT_DIR
MKELEMENT_FILE
CREATED_BRANCH
CHECKED_IN_DIR
COMPLETE
list elements from CREATED_CS to CREATED_BRANCH is really awkward
CREATED_CS
CHECKED_OUT_DIR
MKELEMENT_FILE
CREATED_BRANCH
The bottom question is not about the correctness of the above code, instead i have a script with states and transitions, and i dont want to remember each state by and index, i want to use labels instead.
Thanks!
If you are happy with using a few $ signs here and there, how about you declare some constants:
#!/bin/bash
STATES=(INITIAL DEFAULT_CS_SETUP CREATED_CS CHECKED_OUT_DIR MKELEMENT_FILE CREATED_BRANCH CHECKED_IN_DIR COMPLETE)
tam=${#STATES[#]}
for ((i=0; i < $tam; i++)); do
name=${STATES[i]}
declare -r ${name}=$i
done
echo get the INITIAL state
echo ${STATES[$INITIAL]}
echo get the next state from CREATED_CS
echo ${STATES[$CREATED_CS+1]}
echo list elements from CREATED_CS to the end
for ((i=$CREATED_CS; i < $tam; i++)); do
echo ${STATES[$i]}
done
echo list elements from CREATED_CS to CREATED_BRANCH
for ((i=$CREATED_CS; i <= $CREATED_BRANCH; i++)); do
echo ${STATES[$i]}
done
Related
I am trying to create 1000s of large CSVs rapidly. This function generates the CSVs:
function csvGenerator () {
for ((i=1; i<=$NUMCSVS; i++)); do
CSVNAME=$DIRNAME"-"$CSVPREFIX$i$CSVEXT
HEADERARRAY=()
if [[ ! -e $CSVNAME ]]; then #Only create csv file if it not exist
touch $CSVNAME
echo "file: "$CSVNAME "created at $(date)" >> ../status.txt
fi
for ((j=1; j<=$NUMCOLS; j++)); do
if (( j < $NUMCOLS )) ; then
HEADERNAME=$DIRNAME"-csv-"$i"-header-"$j", "
elif (( j == $NUMCOLS )) ; then
HEADERNAME=$DIRNAME"-csv-"$i"-header-"$j
fi
HEADERARRAY+=$HEADERNAME
done
echo $HEADERARRAY > $CSVNAME
for ((k=1; k<=$NUMROWS; k++)); do
ROWARRAY=()
for ((l=1; l<=$NUMCOLS; l++)); do
if (( l < $NUMCOLS )) ; then
ROWVALUE=$DIRNAME"-csv-"$i"-r"$k"c"$l", "
elif (( l == $NUMCOLS )) ; then
ROWVALUE=$DIRNAME"-csv-"$i"-r"$k"c"$l
fi
ROWARRAY+=$ROWVALUE
done
echo $ROWARRAY >> $CSVNAME
done
done
}
The script takes ~3 mins to generate a CSV with 100k rows and 70 cols. What do I need to do to generate these CSVs at the rate of 1 CSV/~10 seconds?
Let me start by saying that bash and "performant" don't usually go together in the same sentence. As other commentators suggested, awk may be a good choice that's adjacent in some senses.
I haven't yet had a chance to run your code, but it opens and closes the output file once per row — in this example, 100,000 times. Each time it must seek to the end of the file so that it can append the latest row.
Try pulling the actual generation (everything after for ((j=1; j<=$NUMCOLS; j++)); do) into a new function, like generateCsvContents. In that new function, don't reference $CSVNAME, and remove the redirections on the echo statements. Then, in the original function, call the new function and redirect its output to the filename. Roughly:
function csvGenerator () {
for ((i=1; i<=NUMCSVS; i++)); do
CSVNAME=$DIRNAME"-"$CSVPREFIX$i$CSVEXT
if [[ ! -e $CSVNAME ]]; then #Only create csv file if it not exist
echo "file: $CSVNAME created at $(date)" >> ../status.txt
fi
# This will create $CSVNAME if it doesn't yet exist
generateCsvContents > "$CSVNAME"
done
}
function generateCsvContents() {
HEADERARRAY=()
for ((j=1; j<=NUMCOLS; j++)); do
if (( j < NUMCOLS )) ; then
HEADERNAME=$DIRNAME"-csv-"$i"-header-"$j", "
elif (( j == NUMCOLS )) ; then
HEADERNAME=$DIRNAME"-csv-"$i"-header-"$j
fi
HEADERARRAY+=$HEADERNAME
done
echo $HEADERARRAY
for ((k=1; k<=NUMROWS; k++)); do
ROWARRAY=()
for ((l=1; l<=NUMCOLS; l++)); do
if (( l < NUMCOLS )) ; then
ROWVALUE=$DIRNAME"-csv-"$i"-r"$k"c"$l", "
elif (( l == NUMCOLS )) ; then
ROWVALUE=$DIRNAME"-csv-"$i"-r"$k"c"$l
fi
ROWARRAY+=$ROWVALUE
done
echo "$ROWARRAY"
done
}
"Not this way" is I think the answer.
There are a few problems here.
You're not using your arrays as arrays. When you treat them like strings, you affect only the first element in the array, which is misleading.
The way you're using >> causes the output file to be opened and closed once for every line. That's potentially wasteful.
You're not quoting your variables. In fact, you're quoting the stuff that doesn't need quoting, and not quoting the stuff that does.
Upper case variable names are not recommended, due to the risk of collision with system variables. ref
Bash isn't good at this. Really.
A cleaned up version of your function might look like this:
csvGenerator2() {
for (( i=1; i<=NUMCSVS; i++ )); do
CSVNAME="$DIRNAME-$CSVPREFIX$i$CSVEXT"
# Only create csv file if it not exist
[[ -e "$CSVNAME" ]] && continue
touch "$CSVNAME"
date "+[%F %T] created: $CSVNAME" | tee -a status.txt >&2
HEADER=""
for (( j=1; j<=NUMCOLS; j++ )); do
printf -v HEADER '%s, %s-csv-%s-header-%s' "$HEADER" "$DIRNAME" "$i" "$j"
done
echo "${HEADER#, }" > "$CSVNAME"
for (( k=1; k<=NUMROWS; k++ )); do
ROW=""
for (( l=1; l<=NUMCOLS; l++ )); do
printf -v ROW '%s, %s-csv-%s-r%sc%s' "$ROW" "$DIRNAME" "$i" "$k" "$l"
done
echo "${ROW#, }"
done >> "$CSVNAME"
done
}
(Note that I haven't switched the variables to lower case because I'm lazy, but it's still a good idea.)
And if you were to make something functionally equivalent in awk:
csvGenerator3() {
awk -v NUMCSVS="$NUMCSVS" -v NUMCOLS="$NUMCOLS" -v NUMROWS="$NUMROWS" -v DIRNAME="$DIRNAME" -v CSVPREFIX="$CSVPREFIX" -v CSVEXT="$CSVEXT" '
BEGIN {
for ( i=1; i<=NUMCSVS; i++) {
out=sprintf("%s-%s%s%s", DIRNAME, CSVPREFIX, i, CSVEXT)
if (!system("test -e " CSVNAME)) continue
system("date '\''+[%F %T] created: " out "'\'' | tee -a status.txt >&2")
comma=""
for ( j=1; j<=NUMCOLS; j++ ) {
printf "%s%s-csv-%s-header-%s", comma, DIRNAME, i, j > out
comma=", "
}
printf "\n" >> out
for ( k=1; k<=NUMROWS; k++ ) {
comma=""
for ( l=1; l<=NUMCOLS; l++ ) {
printf "%s%s-csv-%s-r%sc%s", comma, DIRNAME, i, k, l >> out
comma=", "
}
printf "\n" >> out
}
}
}
'
}
Note that awk does not suffer from the same open/closer overhead mentioned earlier with bash; when a file is used for output or as a pipe, it gets opened once and is left open until it is closed.
Comparing the two really highlights the choice you need to make:
$ time bash -c '. file; NUMCSVS=1 NUMCOLS=10 NUMROWS=100000 DIRNAME=2 CSVPREFIX=x CSVEXT=.csv csvGenerator2'
[2019-03-29 23:57:26] created: 2-x1.csv
real 0m30.260s
user 0m28.012s
sys 0m1.395s
$ time bash -c '. file; NUMCSVS=1 NUMCOLS=10 NUMROWS=100000 DIRNAME=3 CSVPREFIX=x CSVEXT=.csv csvGenerator3'
[2019-03-29 23:58:23] created: 3-x1.csv
real 0m4.994s
user 0m3.297s
sys 0m1.639s
Note that even my optimized bash version is only a little faster than your original code.
Refactoring your two inner for-loops to loops like this will save time:
for ((j=1; j<$NUMCOLS; ++j)); do
HEADERARRAY+=$DIRNAME"-csv-"$i"-header-"$j", "
done
HEADERARRAY+=$DIRNAME"-csv-"$i"-header-"$NUMCOLS
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 )) ]}
Having an issue where my script won't iterate through my array and wont display correctly
#!/bin/bash
mispelledWords=$(cat $1|aspell list)
ARRAY=( $mispelledWords )
for((i=0; i<${#ARRAY[#]}; i++))
do
echo "'${ARRAY[i]}' is mispelled. Press 'Enter' to keep"
read -p 'this spelling, or type a correction here:' UserWord
if [[ $UserWord == "" ]]; then
echo ${ARRAY[i]} >> .memory
else
UserWordsArray[i]=$UserWord
mispelledArray[i]=${ARRAY[i]}
echo $mispelledArray[i]
fi
done
echo "MISPELLED: CORRECTIONS"
for ((i=0; i<${#UserWord[#]}; i++))
do
echo "${mispelledArray[i]} ${UserWordsArray[i]}"
done
At the point where the array is supposed to be iterating through in the for loop, it only stores one value and when trying to display it in the else statement
echo $mispelledArray[i]
It only displays one of the values passed into the program through another textfile.
Any help would be extremely helpful
Why doesn't my for loop: for ((i=1 ;i<=$n, i++)) work? I can not figure this out. when n is for example 4, the line echo $n returns 4 but it does not go into the loop again. I don't get any errors either. I tried to make a small loop like:
for ((i=1, i<=$n; i++)); do
echo "this works"
done
This works fine which makes it even stranger to me :/. Thanks in advance
read n
length=16
p=()
p[1]=50
rest=63
function s() {
arr=($#)
line="_____________________________________________________________________"
for i in ${arr[#]}; do
line=$( echo $line | sed s/./1/$i)
done
echo $line
}
for ((i=1; i<=$n; i++)); do
echo $n
for ((j=1; j<=$length; j++)); do
s ${p[#]}
done
len=${#p[#]}
((len=$len*2))
for ((k=1; k<=$len; k+=2)); do
((p[$k+1]=p[$k]+1))
((p[$k]=p[$k]-1))
done
for ((l=1; l<=$length; l++)); do
s ${p[#]}
len=${#p[#]}
for ((m=1; m<=$len; m+=2)); do
((p[$m+1]=p[$m+1]+1))
((p[$m]=p[$m]-1))
done
done
((rest=$rest-2*$length))
((length=$length/2))
done
If you don't declare your variables local, they're global -- so the loop in your s function is overwriting the same i counter used outside the function, leading to the outer loop's early exit.
Consider using the below code:
s() {
local -a arr # declare a function-local array (sparse, like all bash arrays)
local i # ...and a function-local counter
for i; do # by default, a for statement iterates over "$#"; that works for us.
arr[$i]=1 # specifically set a value only for named items
done
# ...thereafter, iterate through the range of characters we want to print...
for ((i=0; i<70; i++)); do
printf '%s' "${arr[$i]:-_}" # and print the array entry if present, or an _ otherwise
done
printf '\n' # ...followed by a trailing newline.
}
I can't seem to be able to increase the variable value by 1. I have looked at tutorialspoint's Unix / Linux Shell Programming tutorial but it only shows how to add together two variables.
I have tried the following methods but they don't work:
i=0
$i=$i+1 # doesn't work: command not found
echo "$i"
$i='expr $i+1' # doesn't work: command not found
echo "$i"
$i++ # doesn't work*, command not found
echo "$i"
How do I increment the value of a variable by 1?
You can use an arithmetic expansion like so:
i=$((i+1))
or declare i as an integer variable and use the += operator for incrementing its value.
declare -i i=0
i+=1
or use the (( construct.
((i++))
The way to use expr:
i=0
i=`expr $i + 1`
The way to use i++ (unless you're running with -e/-o errexit):
((i++)); echo $i;
Tested in gnu bash.
you can use bc as it can also do floats
var=$(echo "1+2"|bc)
These are the methods I know:
ichramm#NOTPARALLEL ~$ i=10; echo $i;
10
ichramm#NOTPARALLEL ~$ ((i+=1)); echo $i;
11
ichramm#NOTPARALLEL ~$ ((i=i+1)); echo $i;
12
ichramm#NOTPARALLEL ~$ i=`expr $i + 1`; echo $i;
13
Note the spaces in the last example, also note that's the only one that uses $i.