I'm wondering how to declare a 2D array in bash and then initialize to 0.
In C it looks like this:
int a[4][5] = {0};
And how do I assign a value to an element? As in C:
a[2][3] = 3;
You can simulate them for example with hashes, but need care about the leading zeroes and many other things. The next demonstration works, but it is far from optimal solution.
#!/bin/bash
declare -A matrix
num_rows=4
num_columns=5
for ((i=1;i<=num_rows;i++)) do
for ((j=1;j<=num_columns;j++)) do
matrix[$i,$j]=$RANDOM
done
done
f1="%$((${#num_rows}+1))s"
f2=" %9s"
printf "$f1" ''
for ((i=1;i<=num_rows;i++)) do
printf "$f2" $i
done
echo
for ((j=1;j<=num_columns;j++)) do
printf "$f1" $j
for ((i=1;i<=num_rows;i++)) do
printf "$f2" ${matrix[$i,$j]}
done
echo
done
the above example creates a 4x5 matrix with random numbers and print it transposed, with the example result
1 2 3 4
1 18006 31193 16110 23297
2 26229 19869 1140 19837
3 8192 2181 25512 2318
4 3269 25516 18701 7977
5 31775 17358 4468 30345
The principle is: Creating one associative array where the index is an string like 3,4. The benefits:
it's possible to use for any-dimension arrays ;) like: 30,40,2 for 3 dimensional.
the syntax is close to "C" like arrays ${matrix[2,3]}
Bash doesn't have multi-dimensional array. But you can simulate a somewhat similar effect with associative arrays. The following is an example of associative array pretending to be used as multi-dimensional array:
declare -A arr
arr[0,0]=0
arr[0,1]=1
arr[1,0]=2
arr[1,1]=3
echo "${arr[0,0]} ${arr[0,1]}" # will print 0 1
If you don't declare the array as associative (with -A), the above won't work. For example, if you omit the declare -A arr line, the echo will print 2 3 instead of 0 1, because 0,0, 1,0 and such will be taken as arithmetic expression and evaluated to 0 (the value to the right of the comma operator).
Bash does not support multidimensional arrays.
You can simulate it though by using indirect expansion:
#!/bin/bash
declare -a a0=(1 2 3 4)
declare -a a1=(5 6 7 8)
var="a1[1]"
echo ${!var} # outputs 6
Assignments are also possible with this method:
let $var=55
echo ${a1[1]} # outputs 55
Edit 1: To read such an array from a file, with each row on a line, and values delimited by space, use this:
idx=0
while read -a a$idx; do
let idx++;
done </tmp/some_file
Edit 2: To declare and initialize a0..a3[0..4] to 0, you could run:
for i in {0..3}; do
eval "declare -a a$i=( $(for j in {0..4}; do echo 0; done) )"
done
Another approach is you can represent each row as a string, i.e. mapping the 2D array into an 1D array. Then, all you need to do is unpack and repack the row's string whenever you make an edit:
# Init a 4x5 matrix
a=("00 01 02 03 04" "10 11 12 13 14" "20 21 22 23 24" "30 31 32 33 34")
aset() {
row=$1
col=$2
value=$3
IFS=' ' read -r -a rowdata <<< "${a[$row]}"
rowdata[$col]=$value
a[$row]="${rowdata[#]}"
}
aget() {
row=$1
col=$2
IFS=' ' read -r -a rowdata <<< "${a[$row]}"
echo ${rowdata[$col]}
}
aprint() {
for rowdata in "${a[#]}"; do
echo $rowdata
done
}
echo "Matrix before change"
aprint
# Outputs: a[2][3] == 23
echo "a[2][3] == $( aget 2 3 )"
echo "a[2][3] = 9999"
aset 2 3 9999
# Show result
echo "Matrix after change"
aprint
Outputs:
Matrix before change
00 01 02 03 04
10 11 12 13 14
20 21 22 23 24
30 31 32 33 34
a[2][3] == 23
a[2][3] = 9999
Matrix after change
00 01 02 03 04
10 11 12 13 14
20 21 22 9999 24
30 31 32 33 34
You can also approach this in a much less smarter fashion
q=()
q+=( 1-2 )
q+=( a-b )
for set in ${q[#]};
do
echo ${set%%-*}
echo ${set##*-}
done
of course a 22 line solution or indirection is probably the better way to go and why not sprinkle eval every where to .
2D array can be achieved in bash by declaring 1D array and then elements can be accessed using (r * col_size) + c). Below logic delcares 1D array (str_2d_arr) and prints as 2D array.
col_size=3
str_2d_arr=()
str_2d_arr+=('abc' '200' 'xyz')
str_2d_arr+=('def' '300' 'ccc')
str_2d_arr+=('aaa' '400' 'ddd')
echo "Print 2D array"
col_count=0
for elem in ${str_2d_arr[#]}; do
if [ ${col_count} -eq ${col_size} ]; then
echo ""
col_count=0
fi
echo -e "$elem \c"
((col_count++))
done
echo ""
Output is
Print 2D array
abc 200 xyz
def 300 ccc
aaa 400 ddd
Below logic is very useful to get each row from the above declared 1D array str_2d_arr.
# Get nth row and update to 2nd arg
get_row_n()
{
row=$1
local -n a=$2
start_idx=$((row * col_size))
for ((i = 0; i < ${col_size}; i++)); do
idx=$((start_idx + i))
a+=(${str_2d_arr[${idx}]})
done
}
arr=()
get_row_n 0 arr
echo "Row 0"
for e in ${arr[#]}; do
echo -e "$e \c"
done
echo ""
Output is
Row 0
abc 200 xyz
A way to simulate arrays in bash (it can be adapted for any number of dimensions of an array):
#!/bin/bash
## The following functions implement vectors (arrays) operations in bash:
## Definition of a vector <v>:
## v_0 - variable that stores the number of elements of the vector
## v_1..v_n, where n=v_0 - variables that store the values of the vector elements
VectorAddElementNext () {
# Vector Add Element Next
# Adds the string contained in variable $2 in the next element position (vector length + 1) in vector $1
local elem_value
local vector_length
local elem_name
eval elem_value=\"\$$2\"
eval vector_length=\$$1\_0
if [ -z "$vector_length" ]; then
vector_length=$((0))
fi
vector_length=$(( vector_length + 1 ))
elem_name=$1_$vector_length
eval $elem_name=\"\$elem_value\"
eval $1_0=$vector_length
}
VectorAddElementDVNext () {
# Vector Add Element Direct Value Next
# Adds the string $2 in the next element position (vector length + 1) in vector $1
local elem_value
local vector_length
local elem_name
eval elem_value="$2"
eval vector_length=\$$1\_0
if [ -z "$vector_length" ]; then
vector_length=$((0))
fi
vector_length=$(( vector_length + 1 ))
elem_name=$1_$vector_length
eval $elem_name=\"\$elem_value\"
eval $1_0=$vector_length
}
VectorAddElement () {
# Vector Add Element
# Adds the string contained in the variable $3 in the position contained in $2 (variable or direct value) in the vector $1
local elem_value
local elem_position
local vector_length
local elem_name
eval elem_value=\"\$$3\"
elem_position=$(($2))
eval vector_length=\$$1\_0
if [ -z "$vector_length" ]; then
vector_length=$((0))
fi
if [ $elem_position -ge $vector_length ]; then
vector_length=$elem_position
fi
elem_name=$1_$elem_position
eval $elem_name=\"\$elem_value\"
if [ ! $elem_position -eq 0 ]; then
eval $1_0=$vector_length
fi
}
VectorAddElementDV () {
# Vector Add Element
# Adds the string $3 in the position $2 (variable or direct value) in the vector $1
local elem_value
local elem_position
local vector_length
local elem_name
eval elem_value="$3"
elem_position=$(($2))
eval vector_length=\$$1\_0
if [ -z "$vector_length" ]; then
vector_length=$((0))
fi
if [ $elem_position -ge $vector_length ]; then
vector_length=$elem_position
fi
elem_name=$1_$elem_position
eval $elem_name=\"\$elem_value\"
if [ ! $elem_position -eq 0 ]; then
eval $1_0=$vector_length
fi
}
VectorPrint () {
# Vector Print
# Prints all the elements names and values of the vector $1 on sepparate lines
local vector_length
vector_length=$(($1_0))
if [ "$vector_length" = "0" ]; then
echo "Vector \"$1\" is empty!"
else
echo "Vector \"$1\":"
for ((i=1; i<=$vector_length; i++)); do
eval echo \"[$i]: \\\"\$$1\_$i\\\"\"
###OR: eval printf \'\%s\\\n\' \"[\$i]: \\\"\$$1\_$i\\\"\"
done
fi
}
VectorDestroy () {
# Vector Destroy
# Empties all the elements values of the vector $1
local vector_length
vector_length=$(($1_0))
if [ ! "$vector_length" = "0" ]; then
for ((i=1; i<=$vector_length; i++)); do
unset $1_$i
done
unset $1_0
fi
}
##################
### MAIN START ###
##################
## Setting vector 'params' with all the parameters received by the script:
for ((i=1; i<=$#; i++)); do
eval param="\${$i}"
VectorAddElementNext params param
done
# Printing the vector 'params':
VectorPrint params
read temp
## Setting vector 'params2' with the elements of the vector 'params' in reversed order:
if [ -n "$params_0" ]; then
for ((i=1; i<=$params_0; i++)); do
count=$((params_0-i+1))
VectorAddElement params2 count params_$i
done
fi
# Printing the vector 'params2':
VectorPrint params2
read temp
## Getting the values of 'params2'`s elements and printing them:
if [ -n "$params2_0" ]; then
echo "Printing the elements of the vector 'params2':"
for ((i=1; i<=$params2_0; i++)); do
eval current_elem_value=\"\$params2\_$i\"
echo "params2_$i=\"$current_elem_value\""
done
else
echo "Vector 'params2' is empty!"
fi
read temp
## Creating a two dimensional array ('a'):
for ((i=1; i<=10; i++)); do
VectorAddElement a 0 i
for ((j=1; j<=8; j++)); do
value=$(( 8 * ( i - 1 ) + j ))
VectorAddElementDV a_$i $j $value
done
done
## Manually printing the two dimensional array ('a'):
echo "Printing the two-dimensional array 'a':"
if [ -n "$a_0" ]; then
for ((i=1; i<=$a_0; i++)); do
eval current_vector_lenght=\$a\_$i\_0
if [ -n "$current_vector_lenght" ]; then
for ((j=1; j<=$current_vector_lenght; j++)); do
eval value=\"\$a\_$i\_$j\"
printf "$value "
done
fi
printf "\n"
done
fi
################
### MAIN END ###
################
If each row of the matrix is the same size, then you can simply use a linear array and multiplication.
That is,
a=()
for (( i=0; i<4; ++i )); do
for (( j=0; j<5; ++j )); do
a[i*5+j]=0
done
done
Then your a[2][3] = 3 becomes
a[2*5+3] = 3
This approach might be worth turning into a set of functions, but since you can't pass arrays to or return arrays from functions, you would have to use pass-by-name and sometimes eval. So I tend to file multidimensional arrays under "things bash is simply Not Meant To Do".
One can simply define two functions to write ($4 is the assigned value) and read a matrix with arbitrary name ($1) and indexes ($2 and $3) exploiting eval and indirect referencing.
#!/bin/bash
matrix_write () {
eval $1"_"$2"_"$3=$4
# aux=$1"_"$2"_"$3 # Alternative way
# let $aux=$4 # ---
}
matrix_read () {
aux=$1"_"$2"_"$3
echo ${!aux}
}
for ((i=1;i<10;i=i+1)); do
for ((j=1;j<10;j=j+1)); do
matrix_write a $i $j $[$i*10+$j]
done
done
for ((i=1;i<10;i=i+1)); do
for ((j=1;j<10;j=j+1)); do
echo "a_"$i"_"$j"="$(matrix_read a $i $j)
done
done
Mark Reed suggested a very good solution for 2D arrays (matrix)! They always can be converted in a 1D array (vector). Although Bash doesn't have a native support for 2D arrays, it's not that hard to create a simple ADT around the mentioned principle.
Here is a barebone example with no argument checks, etc, just to keep the solution clear: the array's size is set as two first elements in the instance (documentation for the Bash module that implements a matrix ADT, https://github.com/vorakl/bash-libs/blob/master/src.docs/content/pages/matrix.rst )
#!/bin/bash
matrix_init() {
# matrix_init instance x y data ...
declare -n self=$1
declare -i width=$2 height=$3
shift 3;
self=(${width} ${height} "$#")
}
matrix_get() {
# matrix_get instance x y
declare -n self=$1
declare -i x=$2 y=$3
declare -i width=${self[0]} height=${self[1]}
echo "${self[2+y*width+x]}"
}
matrix_set() {
# matrix_set instance x y data
declare -n self=$1
declare -i x=$2 y=$3
declare data="$4"
declare -i width=${self[0]} height=${self[1]}
self[2+y*width+x]="${data}"
}
matrix_destroy() {
# matrix_destroy instance
declare -n self=$1
unset self
}
# my_matrix[3][2]=( (one, two, three), ("1 1" "2 2" "3 3") )
matrix_init my_matrix \
3 2 \
one two three \
"1 1" "2 2" "3 3"
# print my_matrix[2][0]
matrix_get my_matrix 2 0
# print my_matrix[1][1]
matrix_get my_matrix 1 1
# my_matrix[1][1]="4 4 4"
matrix_set my_matrix 1 1 "4 4 4"
# print my_matrix[1][1]
matrix_get my_matrix 1 1
# remove my_matrix
matrix_destroy my_matrix
For simulating a 2-dimensional array, I first load the first n-elements (the elements of the first column)
local pano_array=()
i=0
for line in $(grep "filename" "$file")
do
url=$(extract_url_from_xml $line)
pano_array[i]="$url"
i=$((i+1))
done
To add the second column, I define the size of the first column and calculate the values in an offset variable
array_len="${#pano_array[#]}"
i=0
while [[ $i -lt $array_len ]]
do
url="${pano_array[$i]}"
offset=$(($array_len+i))
found_file=$(get_file $url)
pano_array[$offset]=$found_file
i=$((i+1))
done
The below code will definitely work provided if you are working on a Mac you have bash version 4. Not only can you declare 0 but this is more of a universal approach to dynamically accepting values.
2D Array
declare -A arr
echo "Enter the row"
read r
echo "Enter the column"
read c
i=0
j=0
echo "Enter the elements"
while [ $i -lt $r ]
do
j=0
while [ $j -lt $c ]
do
echo $i $j
read m
arr[${i},${j}]=$m
j=`expr $j + 1`
done
i=`expr $i + 1`
done
i=0
j=0
while [ $i -lt $r ]
do
j=0
while [ $j -lt $c ]
do
echo -n ${arr[${i},${j}]} " "
j=`expr $j + 1`
done
echo ""
i=`expr $i + 1`
done
Related
This question already has answers here:
Return value in a Bash function
(11 answers)
Closed 9 months ago.
I have made the following function to calculate the maximum value of an array:
MaxArray(){
aux ="$1"
for n in "$#"
do
[ "$n" -gt "$aux" ] && aux="$n"
done
return $aux
}
I tried changing return to echo "$aux" to see if it calculated the value correctly and it worked. With an array with the following values: (1, 10, 152, 0, 3), I tried return and called the function like this:
value=$( MaxArray ${array[#]} )
echo "value is $value"
But $value is empty after calling the function.
How can I assign the return of the function to $value correctly?
Replace
return $aux
with
echo "$aux"
Command substitution captures the standard output, not the exit status.
As an aside, variable assignments should not have spaces before or after the equal sign, the assignment should be
aux="$1"
You don't return a value with return which is used to return an exit status. You return a value by sending it to the standard output (echo, printf...) If your bash is recent enough a better solution that avoids a sub-shell is to pass the variable name as a function argument and use a named reference (local -n var=name):
MaxArray(){
local -n tmp="$1"
local -i n
tmp="$2"
shift 2
for n in "$#"; do
[ "$n" -gt "$tmp" ] && tmp="$n"
done
}
Then:
$ unset value
$ MaxArray value 1 2 3 4 5 4 3 2 1
$ echo $value
5
Note that if your values are stored in an indexed array you can use the same principle to pass its name instead of its content:
$ MaxArray(){
local -n tmp="$1"
local -n arr="$2"
local -i n
tmp="${arr[0]}"
for n in "${arr[#]}"; do
[ "$n" -gt "$tmp" ] && tmp="$n"
done
}
$ declare -a values=( 1 2 3 4 5 4 3 2 1 )
$ unset value
$ MaxArray value values
$ echo $value
5
In the shell, passing values is done through the output, not the return code:
#!/bin/bash
MaxArray() {
local n aux="$1"
for n in "$#"
do
(( n > aux )) && aux=$n
done
echo "$aux"
}
value=$( MaxArray "${array[#]}" )
There are multiple methods for functions to return values:
1st method is to provide the result in a global variable. This method also works for all POSIX-shell variants:
#!/bin/sh
# Here we use the global ret variable to get the result of the function
ret=
max() {
ret=$1
shift
for n
do
[ "$n" -gt "$ret" ] && ret=$n
done
}
max 1 8 2 7 42 23 17
printf 'The maximum of %s is %s\n' '1 8 2 7 42 23 17' "$ret"
The other common method is also POSIX-shell friendly. It involves streaming the result to stdout (printing it), and capturing it into a variable when calling the function. This involves a sub-shell and is to be avoided in a loop for this reason:
#!/bin/sh
max() {
gtst=$1
shift
for n
do
[ "$n" -gt "$gtst" ] && gtst=$n
done
# Print the result
printf %s "$gtst"
}
# Capture the printout of max into the ret variable
ret=$(max 1 8 2 7 42 23 17)
printf 'The maximum of %s is %s\n' '1 8 2 7 42 23 17' "$ret"
The third method involves addressing a variable indirectly by its name, using eval:
#!/bin/sh
max() {
# Get the name of the return variable
ret_name=$1
shift
gtst=$1
shift
for n
do
[ "$n" -gt "$gtst" ] && gtst=$n
done
# Evaluate expression to assign returned value to provided variable name
eval "$ret_name=$gtst"
}
# will get the result into the variable ret
max ret 1 8 2 7 42 23 17
# shellcheck disable=SC2154 # dynamically referrenced ret
printf 'The maximum of %s is %s\n' '1 8 2 7 42 23 17' "$ret"
Finally this method use nameref variables which are only available to Bash starting at version 4.2:
#!/bin/bash
max() {
# Get the name of the return variable into a nameref variable
local -n gtst=$1
local -i n
shift
gtst=$1
shift
for n
do
declare -p gtst
[ "$n" -gt "$gtst" ] && gtst=$n
done
}
# will get the result into the variable ret
declare -i ret
max ret 1 8 2 7 42 23 17
# shellcheck disable=SC2154 # dynamically referrenced ret
printf 'The maximum of %s is %s\n' '1 8 2 7 42 23 17' "$ret"
Same output for all versions:
The maximum of 1 8 2 7 42 23 17 is 42
I have to write a script in bash where I have to provide 10 numbers to the table. Then script have to write the content out and arithmetic mean of even numbers. I did like 90% of the script, but I can't find out how to extract information about quantity of even numbers that is needed for arithmetic mean.
Here is my code:
echo "Provide data:"
i=0
for (( i = 0 ; i < 10; i++ ))
do
echo "Provide $[$i+1] number:"
read x
if [ "$x" = "" ]
then
break
else
table[$i]=$x
fi
done
echo "Provided data: ${table[*]}"
result=0
for (( i = 0 ; i < 10; i++))
do
res=$[${table[i]}%2]
if [ $res -eq 0 ]
then
echo "Number ${table[i]} is even"
result=$[$result+${table[$i]}]
fi
done
echo "SUM:$[$result]"
ignoring data input adding only odd inputs can look like :
$ cat c.sh
#!/bin/bash
declare -A xDarray
sum=0
xDarray[0 1]=1
xDarray[0 2]=3
xDarray[1 0]=2
xDarray[2 0]=4
for var in ${xDarray[#]}
do
if [ $(( $var & 1 )) == 0 ] ; then
echo $var is even
i+=1
tab[$i]=$var
sum=$(( $sum + $var))
fi
done
var=$(echo ${tab[#]} | sed 's/ / + /g' )
echo $var = $sum
result in
$ ./c.sh
2 is even
4 is even
2 + 4 = 6
$
whatever the number of data is used it would work
I let you work around your data input
Here are some suggested modifications for your script, syntax is simple.
#!/bin/bash
arr=()
for (( i=1;i<=10;i++ )); do
number=''
while [[ ! $number =~ ^[0-9]+$ ]]; do
printf "Please enter number $i:\n"
read number
done
arr+=($number)
done
printf "\nProvided numbers:"
printf " %d" "${arr[#]}"
printf "\nEven numbers:"
s=0
n=0
for x in "${arr[#]}"; do
if ! (( x % 2 )); then
printf " %d" "$x"
s=$(( s + x ))
(( n++ ))
fi
done
m=$(( s / n ))
printf "\nMean of the %d even numbers: %d / %d = %d\n" "$n" "$s" "$n" "$m"
Use an array arr to hold the input numbers, declare with arr=(), append numbers with arr+=($x), we refer only ${arr[#]} for all the items and we avoid any other complex array references, indices etc.
Every input number is tested against regular expression ^[0-9]+$ which means one or more digits (and only digits) with the =~ operator, and if this is not true, we prompt again for the same i-th input number.
Also we prefer printf for printing.
The last loop is the standard array loop, where we use again the arithmetic expansion syntax to find the even numbers, to add them to the sum and get the mean of them (result is an integer).
If you want to print a decimal result, e.g. with 2 floating points, you could use bc and printf "%f" like this:
m=$( bc <<< "scale=2; $s/$n" )
printf "%.2f" "$m"
Is it possible to write a script that reads the file containing numbers (one per line) and writes their maximum, minimum and sum. If the file is empty, it will print an appropriate message. The name of the file is to be given as the parameter of the script. I mange to create below script, but there are 2 errors:
./4.3: line 20: syntax error near unexpected token `done'
./4.3: line 20: `done echo "Max: $max" '
Is it possible to add multiple files as parameter?
lines=`cat "$1" | wc -l`
if [ $lines -eq 0 ];
then echo "File $1 is empty!"
exit fi min=`cat "$1" | head -n 1`
max=$min sum=0
while [ $lines -gt 0 ];
do num=`cat "$1" |
tail -n $lines`
if [ $num -gt $max ];
then max=$num
elif [ $num -lt $min ];
then min=$num fiS
sum=$[ $sum + $num] lines=$[ $lines - 1 ]
done echo "Max: $max"
echo "Min: number $min"
echo "Sum: $sum"
Pretty compelling use of GNU datamash here:
read sum min max < <( datamash sum 1 min 1 max 1 < "$1" )
[[ -z $sum ]] && echo "file is empty"
echo "sum=$sum; min=$min; max=$max"
Or, sort and awk:
sort -n "$1" | awk '
NR == 1 { min = $1 }
{ sum += $1 }
END {
if (NR == 0) {
print "file is empty"
} else {
print "min=" min
print "max=" $1
print "sum=" sum
}
}
'
Here's how I'd fix your original attempt, preserving as much of the intent as possible:
#!/usr/bin/env bash
lines=$(wc -l "$1")
if [ "$lines" -eq 0 ]; then
echo "File $1 is empty!"
exit
fi
min=$(head -n 1 "$1")
max=$min
sum=0
while [ "$lines" -gt 0 ]; do
num=$(tail -n "$lines" "$1")
if [ "$num" -gt "$max" ]; then
max=$num
elif [ "$num" -lt "$min" ]; then
min=$num
fi
sum=$(( sum + num ))
lines=$(( lines - 1 ))
done
echo "Max: $max"
echo "Min: number $min"
echo "Sum: $sum"
The dealbreakers were missing linebreaks (can't use exit fi on a single line without ;); other changes are good practice (quoting expansions, useless use of cat), but wouldn't have prevented your script from working; and others are cosmetic (indentation, no backticks).
The overall approach is a massive antipattern, though: you read the whole file for each line being processed.
Here's how I would do it instead:
#!/usr/bin/env bash
for fname in "$#"; do
[[ -s $fname ]] || { echo "file $fname is empty" >&2; continue; }
IFS= read -r min < "$fname"
max=$min
sum=0
while IFS= read -r num; do
(( sum += num ))
(( max = num > max ? num : max ))
(( min = num < min ? num : min ))
done < "$fname"
printf '%s\n' "$fname:" " min: $min" " max: $max" " sum: $sum"
done
This uses the proper way to loop over an input file and utilizes the ternary operator in the arithmetic context.
The outermost for loop loops over all arguments.
You can do the whole thing in one while loop inside a shell script. Here's the bash version:
s=0
while read x; do
if [ ! $mi ]; then
mi=$x
elif [ $mi -gt $x ]; then
mi=$x
fi
if [ ! $ma ]; then
ma=$x
elif [ $ma -lt $x ]; then
ma=$x
fi
s=$((s+x))
done
if [ ! $ma ]; then
echo "File is empty."
else
echo "s=$s, mi=$mi, ma=$ma"
fi
Save that script into a file, and then you can use pipes to send as many input files into it as you wish, like so (assuming the script is called "mysum"):
cat file1 file2 file3 | mysum
or for a single file
mysum < file1
(Make sure, the script is executable and on the $PATH, otherwise use "./mysum" for the script in the current directory or indeed "bash mysum" if it isn't executable.)
The script assumes that the numbers are one per line and that there's nothing else on the line. It gives a message if the input is empty.
How does it work? The "read x" will take input from stdin line-by-line. If the file is empty, the while loop will never be run, and thus variables mi and ma won't be set. So we use this at the end to trigger the appropriate message. Otherwise the loop checks first if the mi and ma variables exist. If they don't, they are initialised with the first x. Otherwise it is checked if the next x requires updating the mi and ma found thus far.
Note that this trick ensures that you can feed-in any sequence of numbers. Otherwise you have to initialise mi with something that's definitely too large and ma with something that's definitely too small - which works until you encounter a strange number list.
Note further, that this works for integers only. If you need to work with floats, then you need to use some other tool than the shell, e.g. awk.
Just for fun, here's the awk version, a one-liner, use as-is or in a script, and it will work with floats, too:
cat file1 file2 file3 | awk 'BEGIN{s=0}; {s+=$1; if(length(mi)==0)mi=$1; if(length(ma)==0)ma=$1; if(mi>$1)mi=$1; if(ma<$1)ma=$1} END{print s, mi, ma}'
or for one file:
awk 'BEGIN{s=0}; {s+=$1; if(length(mi)==0)mi=$1; if(length(ma)==0)ma=$1; if(mi>$1)mi=$1; if(ma<$1)ma=$1} END{print s, mi, ma}' < file1
Downside: if doesn't give a decent error message for an empty file.
a script that reads the file containing numbers (one per line) and writes their maximum, minimum and sum
Bash solution using sort:
<file sort -n | {
read -r sum
echo "Min is $sum"
while read -r num; do
sum=$((sum+num));
done
echo "Max is $num"
echo "Sum is $sum"
}
Let's speed up by using some smart parsing using tee, tr and calculating with bc and if we don't mind using stderr for output. But we could do a little fifo and synchronize tee output. Anyway:
{
<file sort -n |
tee >(echo "Min is $(head -n1)" >&2) >(echo "Max is $(tail -n1)" >&2) |
tr '\n' '+';
echo 0;
} | bc | sed 's/^/Sum is /'
And there is always datamash. The following willl output 3 numbers, being sum, min and max:
<file datamash sum 1 min 1 max 1
You can try with a shell loop and dc
while [ $# -gt 0 ] ; do
dc -f - -e '
['"$1"' is empty]sa
[la p q ]sZ
z 0 =Z
# if file is empty
dd sb sc
# populate max and min with the first value
[d sb]sY
[d lb <Y ]sM
# if max keep it
[d sc]sX
[d lc >X ]sN
# if min keep it
[lM x lN x ld + sd z 0 <B]sB
lB x
# on each line look for max, min and keep the sum
[max for '"$1"' = ] n lb p
[min for '"$1"' = ] n lc p
[sum for '"$1"' = ] n ld p
# print summary at end of each file
' <"$1"
shift
done
I am trying to perform bubble sort is Unix shell script. Why is my code not working?
a=(10 8 20 25 12)
for ((i=0;i<5;i++))
do
for((j=0;j<5;j++))
do
if ((${a[j]} > ${a[$((j+1))]}))
then
v=${a[$j]}
a[$j]=${a[$((j+1))]}
a[$((j+1))]=$v
fi
done
done
echo ${a[*]}
echo "end..."
I guess this is homework. therefore I don't give codes, just point out the errors in your codes:
for((j=0;j<5;j++)) then read a[j+1], here would be problem because when j=4, j+1 doesn't exist
fix that, your program will sort.
Try this:
echo "Enter size of array";
read n; #get the size of array from user.
echo "Enter the array";
read -a arr; #get the array form user eg: 2 3 4 5 6
echo "Orignal array is: ${arr[*]}"; #print orignal array
flag=1;
for (( i = 0; i < $n-1; i++ ))
do
flag=0;
for ((j = 0; j < $n-1-$i; j++ ))
do
if [[ ${arr[$j]} -gt ${arr[$j+1]} ]]
then
temp=${arr[$j]};
arr[$j]=${arr[$j+1]};
arr[$j+1]=$temp;
flag=1;
fi
done
if [[ $flag -eq 0 ]]; then
break;
fi
done
echo "Final sorted Array is ${arr[*]}";
This is my code for a bubble sort on n numbers:
#!/bin/bash
echo -n "Input n, the number of numbers"
read N
declare -a array[N]
echo -e "Input the elements, press enter after each element"
for i in seq 1 $N
do
read array[$i]
done
swap1()
{ # for swapping two numbers, we are employing bubble sort
local temp = ${array[$1]}
array[$1] = ${array[$2]}
array[$2]=$temp
return
}
numb_elements=${#array[#]}
let "comparisons = $numb_elements - 1"
count=1
while [ "$comparisons" -gt 0]
do
index =0
while[ "$index" -lt "$comparisons" ];do
if [ ${array[$index]} \> ${array[ 'expr $index + 1']} ]
then
swap1 $index 'expr $index + 1'
fi
let "index += 1" # Or, index+=1 on Bash, ver. 2.1 or newer
done
let "comparisons -=1"
echo
echo "$count: ${array[#]}
echo
let "count +=1"
done
exit 0
I have two problems with this code:
the input array just takes 3 numbers
and then i get an error on line 42 saying syntax error for the command while do
I have tried while [] ; do, but it doesn't work.
Its just been a day that i have been trying bash syntax.
Moreover do not write
for i in seq 1 $N
which iterate i over the set {"seq","1",$N}, but type
for i in $(seq 1 $N)
to insert the result of the command as part of code.
You forgot the closing quote in this line :
echo "$count: ${array[#]}
Also the code of the nested loops is badly indented, so it is a bit hard to read and debug.
So far I have found the following errors:
while [ "$comparisons" -gt 0 ]
^ missing space here
while [ "$index" -lt "$comparisons" ];do
^ missing space
echo "$count: ${array[#]}"
^ missing quote
Note that in bash [ is equivalent to test command, so a space is required around [ and ] unlike many other programming languages.
You made a series of errors:
correct spaces are fundamental to shell scripting
missing `` apices to execute code and get the output
logic error (starting inserting from the second array element and using it from the first one)
iterating the wrong number of time for the bubblesort alg
This is your code corrected.
#!/bin/bash
swap1() { # for swapping two numbers, we are employing bubble sort
local temp=${array[$1]}
array[$1]=${array[$2]}
array[$2]=$temp
return
}
echo -n "Input n, the number of numbers: "
read N
declare -a array[$N]
echo -e "Input the elements, press enter after each element"
for i in `seq 1 $N`
do
read array[$i]
done
numb_elements=${#array[#]}
#let "comparisons = $numb_elements - 1"
comparisons=$numb_elements
count=1
while [ "$comparisons" -gt 0 ]
do
index=1
while [ "$index" -lt "$comparisons" ]
do
tmp=`expr $index + 1`
if [ ${array[$index]} -gt ${array[$tmp]} ]
then
swap1 $index $tmp
fi
let "index += 1" # Or, index+=1 on Bash, ver. 2.1 or newer
done
let "comparisons -= 1"
echo
echo "$count: ${array[#]}"
echo
let "count += 1"
done
exit 0
Try this:
while [ "$comparisons" -gt 0]
should be (notice space before the closing bracket ]):
while [ "$comparisons" -gt 0 ]