Pascal's triangle in Linux shell script - linux

I'm trying to write a code which receives an integer "n" as a parameter and then print the n-th row of the Pascal's triangle starting from 0, 1,..,n.
for example if the entry is 3, the program prints 1 3 3 1.
So far I wrote a code to get the whole triangle printed, but I can't have just the last row.
This is what I have
echo "Insert the row:" read n for((i=0;i<$n;i++))
do
eval"a$i=($(w=1;v=1
for((j=0;j<$n-$i;j++))
do
[ $i -eq 0 -o $j -eq 0 ]&&{ v=1 && w=1; }||v=$((w+a$((i-1))[$((j))]))
echo -n "$v "
w=$v
done))"
eval echo "$(for((k=0;k<=$i;k++))
do
eval "echo -n \"\$((a\$((i-k))[k])) \""
done)"
done

#!/bin/bash
read -p "Insert the row:" n
typeset -A Tab
for((i=0;i<=$n;i++))
do
Tab[$i,0]=1
Tab[$i,$i]=1
for((j=1;j<$i;j++))
do
a=${Tab[$((i-1)),$((j-1))]}
b=${Tab[$((i-1)),$j]}
Tab[$i,$j]=$(( a + b ))
done
done
#print result
for((j=0;j<=$n;j++))
do
echo -n ${Tab[$n,$j]} " "
done
echo
Test :
Insert the row:3
1 3 3 1

I found an awk solution to that question:
awk -v line_num=5 'BEGIN{for(i=line_num;i<=line_num;i++){c=1;r=c;for(j=0;j<i;j++){c*=(i-j)/(j+1);r=r" "c};print r}}'
Change line_num value to the desired one.
Based on a solution found here.
That's of course if awk counts…

Here is a simple bash script to print pascal's triangle using simple for,if else and echo command:
echo "Enter number of rows : "
read rows
coef=1
for((i=0;i<rows;i++))
do
for((space=1;space<=rows-i; space++))
do
echo -n " "
done
for((j=0;j<=i;j++))
do
if [ $j -eq 0 -o $i -eq 0 ]
then
coef=1;
else
coef=$((coef*(i-j+1)/j))
fi
echo -n $coef " "
done
echo
done

Related

Show progress while sleep in bash

I wrote a simple script which must show progress while user waiting. But I get infinitive loop and seems sleep not working. What wrong in this code?
#!/bin/bash
spinner=(
"Working "
"Working. "
"Working.. "
"Working... "
"Working...."
)
while sleep 10
do
for item in ${spinner[*]}
do
echo -en "\r$item"
sleep .1
echo -en "\r \r"
done
done
One idea:
using the bash (system) variable SECONDS to measure our 10 seconds
using a tput code for ovewriting a line
eliminating the spinner[] array (since the only difference in values is the number of trailing periods)
EraseToEOL=$(tput el)
max=$((SECONDS + 10)) # add 10 seconds to current count
while [ $SECONDS -le ${max} ]
do
msg='Waiting'
for i in {1..5}
do
printf "%s" "${msg}"
msg='.'
sleep .1
done
printf "\r${EraseToEOL}"
done
printf "\n"
A small change to OP's current code using the max/SECONDS approach:
spinner=(
"Working "
"Working. "
"Working.. "
"Working... "
"Working...."
)
max=$((SECONDS + 10))
while [[ ${SECONDS} -le ${max} ]]
do
for item in ${spinner[*]}
do
echo -en "\r$item"
sleep .1
echo -en "\r \r"
done
done
Use the in/decrement variable i to put out the array...
#!/bin/bash
countdown(){
spinner=(
"Working "
"Working. "
"Working.. "
"Working... "
"Working...."
)
i=4
if [ ${i} -lt 5 ]
then
while true
do
for i in ${i}
do
printf "%s \t" ${spinner[i]}
sleep .1
printf "\r"
sleep .1
if [ ${i} -eq 0 ]
then
# Here you can clean up or do what to do at zero count
printf "\n"
unset i
unset spinner
return 0 # Can be used in ${?} from parent bash
else
i=$((${i}-1))
fi
done
done
return 1 # Should never be executed
fi
}
# A funny cd ;-)
cd(){
countdown && printf "%s\n" "DONE changing to "${1} # Gives out if return is 0 (${?})
unset cd
cd ${1}
}
#
cd ~
My method of showing progress while sleeping in bash:
sleep 5 | pv -t
It probably can't get any simpler than that :)
Check out this spiner
Or from this project

linux printf to specified terminal line

In yocto, when I use bibake to build one recipe, stages related with this recipe will be printed in multiple lines, very beautiful.
So I want implement a tiny example like it.
get current cursor's row
run 2 threads, one outputs something at row + 1, another outputs
something at row + 2
As a result, I failed. Then I run 2 process which do same things, failed again.
Can some one give me some suggestions?
What I want is like:
ts:/home/test$ ./program1 &; ./program1 &
program1's output.....................
program2's output.....................
In Bash:
#!/usr/bin/env bash
printf '\n\n'
echo -ne "\033[6n"
read -rs -d\[ _
read -rs -dR foo
cursor_pos=$(cut -d";" -f1 <<< "$foo")
upper_row=$((cursor_pos - 2))
lower_row=$((cursor_pos - 1))
echo -ne "\033[${upper_row};0H"
echo upper row
sleep 2
echo -ne "\033[${lower_row};0H"
echo lower low
sleep 2
echo -ne "\033[${upper_row};0H"
printf "\r\e[0K%s\n" "upper again"
sleep 2
echo -ne "\033[${lower_row};0H"
printf "\r\e[0K%s\n" "lower again"

Bash script: max,min,sum - many sources as parameter

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

In bash, how to store a return value in a variable?

I know some very basic commands in Linux and am trying to write some scripts. I have written a function which evaluates the sum of last 2-digits in a 5-digit number. The function should concatenate this resultant sum in between the last 2-digits and return it. The reason I want to return this value is because I will be using this value in the other function.
Ex: if I have 12345, then my function will calculate 4+5 and return 495.
#!/bin/bash
set -x
echo "enter: "
read input
function password_formula
{
length=${#input}
last_two=${input:length-2:length}
first=`echo $last_two| sed -e 's/\(.\)/\1 /g'|awk '{print $2}'`
second=`echo $last_two| sed -e 's/\(.\)/\1 /g'|awk '{print $1}'`
let sum=$first+$second
sum_len=${#sum}
echo $second
echo $sum
if [ $sum -gt 9 ]
then
sum=${sum:1}
fi
value=$second$sum$first
return $value
}
result=$(password_formula)
echo $result
I am trying to echo and see the result but I am getting the output as shown below.
-bash-3.2$ ./file2.sh
+++ password_formula
+++ echo 'enter: '
+++ read input
12385
+++ length=8
+++ last_two=85
++++ echo 85
++++ sed -e 's/\(.\)/\1 /g'
++++ awk '{print $2}'
+++ first=5
++++ echo 85
++++ sed -e 's/\(.\)/\1 /g'
++++ awk '{print $1}'
+++ second=8
+++ let sum=5+8
+++ sum_len=2
+++ echo 5
+++ echo 8
+++ echo 13
+++ '[' 13 -gt 9 ']'
+++ sum=3
+++ value=835
+++ return 835
++ result='enter:
5
8
13'
++ echo enter: 5 8 13
enter: 5 8 13
I also tried to print the result as:
password_formula
RESULT=$?
echo $RESULT
But that is giving some unknown value:
++ RESULT=67
++ echo 67
67
How can I properly store the correct value and print (to double check) on the screen?
Simplest answer:
the return code from a function can be only a value in the range from 0 to 255 .
To store this value in a variable you have to do like in this example:
#!/bin/bash
function returnfunction {
# example value between 0-255 to be returned
return 23
}
# note that the value has to be stored immediately after the function call :
returnfunction
myreturnvalue=$?
echo "myreturnvalue is "$myreturnvalue
The return value (aka exit code) is a value in the range 0 to 255 inclusive. It's used to indicate success or failure, not to return information. Any value outside this range will be wrapped.
To return information, like your number, use
echo "$value"
To print additional information that you don't want captured, use
echo "my irrelevant info" >&2
Finally, to capture it, use what you did:
result=$(password_formula)
In other words:
echo "enter: "
read input
password_formula()
{
length=${#input}
last_two=${input:length-2:length}
first=`echo $last_two| sed -e 's/\(.\)/\1 /g'|awk '{print $2}'`
second=`echo $last_two| sed -e 's/\(.\)/\1 /g'|awk '{print $1}'`
let sum=$first+$second
sum_len=${#sum}
echo $second >&2
echo $sum >&2
if [ $sum -gt 9 ]
then
sum=${sum:1}
fi
value=$second$sum$first
echo $value
}
result=$(password_formula)
echo "The value is $result"
It is easy you need to echo the value you need to return and then capture it like below
demofunc(){
local variable="hellow"
echo $variable
}
val=$(demofunc)
echo $val
Use the special bash variable "$?" like so:
function_output=$(my_function)
function_return_value=$?
The answer above suggests changing the function to echo data rather than return it so that it can be captured.
For a function or program that you can't modify where the return value needs to be saved to a variable (like test/[, which returns a 0/1 success value), echo $? within the command substitution:
# Test if we're remote.
isRemote="$(test -z "$REMOTE_ADDR"; echo $?)"
# Or:
isRemote="$([ -z "$REMOTE_ADDR" ]; echo $?)"
# Additionally you may want to reverse the 0 (success) / 1 (error) values
# for your own sanity, using arithmetic expansion:
remoteAddrIsEmpty="$([ -z "$REMOTE_ADDR" ]; echo $((1-$?)))"
E.g.
$ echo $REMOTE_ADDR
$ test -z "$REMOTE_ADDR"; echo $?
0
$ REMOTE_ADDR=127.0.0.1
$ test -z "$REMOTE_ADDR"; echo $?
1
$ retval="$(test -z "$REMOTE_ADDR"; echo $?)"; echo $retval
1
$ unset REMOTE_ADDR
$ retval="$(test -z "$REMOTE_ADDR"; echo $?)"; echo $retval
0
For a program which prints data but also has a return value to be saved, the return value would be captured separately from the output:
# Two different files, 1 and 2.
$ cat 1
1
$ cat 2
2
$ diffs="$(cmp 1 2)"
$ haveDiffs=$?
$ echo "Have differences? [$haveDiffs] Diffs: [$diffs]"
Have differences? [1] Diffs: [1 2 differ: char 1, line 1]
$ diffs="$(cmp 1 1)"
$ haveDiffs=$?
$ echo "Have differences? [$haveDiffs] Diffs: [$diffs]"
Have differences? [0] Diffs: []
# Or again, if you just want a success variable, reverse with arithmetic expansion:
$ cmp -s 1 2; filesAreIdentical=$((1-$?))
$ echo $filesAreIdentical
0
It's due to the echo statements. You could switch your echos to prints and return with an echo. Below works
#!/bin/bash
set -x
echo "enter: "
read input
function password_formula
{
length=${#input}
last_two=${input:length-2:length}
first=`echo $last_two| sed -e 's/\(.\)/\1 /g'|awk '{print $2}'`
second=`echo $last_two| sed -e 's/\(.\)/\1 /g'|awk '{print $1}'`
let sum=$first+$second
sum_len=${#sum}
print $second
print $sum
if [ $sum -gt 9 ]
then
sum=${sum:1}
fi
value=$second$sum$first
echo $value
}
result=$(password_formula)
echo $result
Something like this could be used, and still maintaining meanings of return (to return control signals) and echo (to return information) and logging statements (to print debug/info messages).
v_verbose=1
v_verbose_f="" # verbose file name
FLAG_BGPID=""
e_verbose() {
if [[ $v_verbose -ge 0 ]]; then
v_verbose_f=$(tempfile)
tail -f $v_verbose_f &
FLAG_BGPID="$!"
fi
}
d_verbose() {
if [[ x"$FLAG_BGPID" != "x" ]]; then
kill $FLAG_BGPID > /dev/null
FLAG_BGPID=""
rm -f $v_verbose_f > /dev/null
fi
}
init() {
e_verbose
trap cleanup SIGINT SIGQUIT SIGKILL SIGSTOP SIGTERM SIGHUP SIGTSTP
}
cleanup() {
d_verbose
}
init
fun1() {
echo "got $1" >> $v_verbose_f
echo "got $2" >> $v_verbose_f
echo "$(( $1 + $2 ))"
return 0
}
a=$(fun1 10 20)
if [[ $? -eq 0 ]]; then
echo ">>sum: $a"
else
echo "error: $?"
fi
cleanup
In here, I'm redirecting debug messages to separate file, that is watched by tail, and if there is any changes then printing the change, trap is used to make sure that background process always ends.
This behavior can also be achieved using redirection to /dev/stderr, But difference can be seen at the time of piping output of one command to input of other command.
Ok the main answers to this are problematic if we have errexit set, e.g.
#!/bin/bash
set -o errexit
my_fun() {
# returns 0 if the first arguments is "a"
# 1 otherwise
[ "${1}" = "a" ]
}
my_fun "a"
echo "a=$?"
my_fun "b"
echo "b=$?"
In this case bash just exit when the result is not 0, e.g. this only prints the a line.
./test_output.sh
a=0
As already said well here probably the most correct answer is something like this:
# like this
my_val=0 ; my_fun "a" || my_val=$?
echo "a=${my_val}"
# or this
my_fun "b" && my_val=0 || my_val=$?
echo "b=${my_val}"
This print all the values correctly without error
a=0
b=1
I don't know if the "echo" implementation is the most correct, as I still is not clear to me if
$() creates a subshell or not.
I have a function for example that opens up a
file descriptor in a function and return the number and it seems that bash closes the fd after exiting the function.
(if someone can help me here :-)

Create files based on user input

I have a bash script that asks the user for 3 numbers (example, 123).
I'm stuck on how to separate these numbers in order to create file1, file2, file3, I also have to determine if they are unique.
Any help would be appreciated.
I can post my bash script if needed.
! /bin/bash
clear
echo -n "Enter three digits number: "
read number
echo $number | grep "^[0-9][0-9][0-9]$"
if [ "$?" -eq 1 ]
then
echo "Error!! Please enter only 3 numbers."
exit 1
fi
if [ -d ~/a2/numbers ]
then
rm -r ~/a2/numbers
fi
mkdir ~/a2/numbers
if [ ! -e ~/a2/products ]
then
echo "Error the file \'products\'! does not exist"
exit 1
fi
echo ' '
cat ~/a2/products
echo ' '
cut -f2 -d',' ~/a2/products > ~/a2/names
cat ~/a2/names
echo "I have $(cat ~/a2/names | wc -l) products in my product file"
echo ' '
You could use the command fold which will split your string by character. Example:
echo ${number} | fold -w1
To check if they are unique just use the if statement, because in your case you allow only three one digit numbers.
#!/bin/bash
read -p "enter 3 numbers: " nums
if [[ $nums != [0-9][0-9][0-9] ]]; then
echo "digits only please"
exit
fi
read n1 n2 n3 < <(sed 's/./& /g' <<< $nums)
if ((n1 == n2)) || ((n1 == n3)) || ((n2 == n3)); then
echo "no duplicate numbers"
exit
fi

Resources