Using selfwritten .sh file in another .sh file - bash

I am writing a small .sh program in bash.
The problem is extremely simple, i.e, to dind the primefactors of a number.
What I've done is written a .sh file to check if a number is prime or not.
Here is the code for that :
if [ $# -ne 1 ]; then
exit
fi
number=$1
half=$(($number / 2))
for (( i=2;i<$half;i++ ))
do
rem=$(($number % $i))
if [ $rem -eq 0 ]; then
echo "0"
exit
fi
done
echo "1"
And the second .sh file to generate prime factors :
clear
echo "Enter number : "
read number
half=$(($number / 2))
for(( i=1;i<=$half;i++ ))
do
rem=$(($number % $i))
if [ $rem -eq 0 ]; then
ok=`prime.sh $rem`
if [ "$ok" == "1" ]; then
echi $i
fi
fi
done
This line ,
ok=`prime.sh $rem`
gives the following error :
primefactor.sh: line 10: prime.sh: command not found
So, is it not possible to divide a program into smaller modules and use it in the other modules like other programming languages ?
Some help on how to achieve this will be helpful.

primefactor.sh: line 10: prime.sh: command not found
...means that prime.sh is not in your PATH, or is not executable. There are a few ways you can remedy this:
First, ensure that the +x bit is set:
chmod +x prime.sh
...then, add it to your PATH:
PATH=.:$PWD
...or invoke it directly:
ok=$(./prime.sh)
By the way, names ending in .sh are appropriate for POSIX sh libraries, not bash scripts (which typically aren't valid POSIX sh scripts anyhow). You don't run ls.elf; you should run prime, not prime.sh, for the same reasons.
That said, if your goal is just to split your code amongst multiple files, a library might be the right thing. Using subshells (which fork an existing shell instance) is much more efficient than spawning subprocesses (which involve both a fork and an exec).
For instance, you could write prime.bash:
check() {
local number half i rem
number=$1
half=$((number / 2))
for (( i=2; i<half; i++ )); do
rem=$((number % i))
if (( rem == 0 )); then
echo "0"
return
fi
done
echo "1"
}
...and then, in your primefactor script, read in that library and use the function it defined:
source prime.bash # read in the library
clear
echo "Enter number : "
read number
half=$((number / 2))
for(( i=1;i<=half;i++ ))
do
rem=$((number % i))
if (( rem == 0 )); then
ok=$(check "$rem")
if [[ $ok = 1 ]]; then
echo "$i"
fi
fi
done

Call your script like this:
ok=`./prime.sh $rem`

Related

How to use a for loop to create folders in bash

I want to create a directory in which there is a bunch of text files using a for loop. Here is my code:
#!/bin/bash
echo "enter the nums: "
read num1 num2
for (( counter=0; counter<$num2; counter++ ))
do
if [ $num1 -lt 10 ] && [ $num2 -lt 10 ];
then
mkdir $num1 && touch $num1/$num1$num2.txt
echo "$num1""$num2" > $num1/$num1$num2.txt
else
echo "you weren't supposed to do that"
fi
done
What I want to happen if for example the user entered: "2 9"
Make a directory called 2
In it make text files called 290.txt, 291.txt, 292.txt... up till 299.txt.
Instead, what happens right now is it makes the directory and gives an error that the directory already exists. I don't know the next step, please help.
The biggest problem here is that you're doing things inside the loop that really only should be done once. Specifically, the error you're getting is because it tries to create the directory every time through the loop, but you can only create it once. Also, if the user enters too large a number, it'll print multiple error messages (e.g. if num2 is entered as 500, it'll print 500 error messages). You need to do both the error check and creating the directory once, before the loop.
A second problem is that you don't add $counter to the filename, so if the user enters "2 9", it'll create a file named 29.txt nine times.
You also have some more minor issues: in general, error messages should be printed to standard error instead of standard output (you can redirect them with >&2), and if there's an error the script should exit with a nonzero status. Also, you should (almost always) put double-quotes around variable references, to avoid weird results if the variables are blank or contain whitespace or some other things. You also don't need to touch files before writing into them (using > somefile will create the file if it doesn't exist).
With these things fixed (and some stylistic tweaks), here's what I get:
#!/bin/bash
echo "enter the nums: "
read num1 num2
if ! [ "$num1" -lt 10 ] || ! [ "$num2" -lt 10 ]; then
echo "you weren't supposed to do that" >&2 # message send to stderr
exit 1 # exit with error status
fi
mkdir "$num1" || exit $? # if mkdir fails, exit with its error status
for (( counter=0; counter<$num2; counter++ )); do
echo "${num1}${num2}" > "${num1}/${num1}${num2}${counter}.txt"
done
BTW, the ! [ "$num1" -lt 10 ] tests may look a little weird; why not just use [ "$num" -ge 10 ]? I did it that way in case $num1 and/or $num2 isn't a valid number, in which case both -lt and -ge tests would fail; using a negated test makes that an error rather than a success.
I'm not fluent in bash or anything, but it looks like mkdir $num1 is called on every loop. Find out first if the directory exists.
Here you are! change the if and for statement parent and child:
#!/bin/bash
echo "enter the nums: "
read num1 num2
if [ $num1 -lt 10 ] && [ $num2 -lt 10 ]; then
mkdir $num1
for i in $(seq 0 $num2); do
touch $num1/$num1$num2$i.txt
echo "$num1""$num2""$i" > $num1/$num1$num2$i.txt
done
else
echo "you weren't supposed to do that"
fi

If-statements in Bash Syntax issues produce right answers, while right code shows wrong answers [duplicate]

This question already has answers here:
Why should there be spaces around '[' and ']' in Bash?
(5 answers)
Closed 5 years ago.
I am fairly new to bash scripting and am struggling with some if-statement syntax.
I have currently written up the following loop:
for (( i = 2; i < $# - 1; i++)); do
if [ $i -ne 0]; then
if [ $i -ne 1]; then
echo "$i was not 1 or 0. Please correct this then try again."
exit 1;
fi
fi
done
This code is supposed to test whether any arguments after the first are either a 1 or a 0.
While the following errors are printed:
./blink.sh: line 36: [: missing `]'
./blink.sh: line 36: [: missing `]'
...the code actually runs fine afterwards (so the errors don't kill the program).
My understanding, however, is that in bash, you put spaces before and after the expression inside the if statement. So this:
if [ $i -ne 0]; then
Becomes:
if [ $i -ne 0 ]; then
However, running this code produces the following:
2 was not 1 or 0. Please correct this then try again.
The main issue I am having with this stems from not understanding how to indirectly reference the positional arguments provided by the execution command. As such, I am confused as to what syntax must be altered to call the objects the arguments point to (in this case, hopefully either a 1 or a 0) rather than the position of the arguments themselves (argument 1, 2, 3...).
Thanks!
EDIT: Altering the question to better fit the advice #randomir provided and clear up what the actual question entails
Based on:
This code is supposed to test whether any arguments after the first are either a 1 or a 0.
I'm assuming you're trying to access positional arguments $2, $3, etc. To make your for loop solution work, you would have to use an indirect reference: ${!i} (see shell parameter expansion). For example, this should work:
#!/bin/bash
for (( i = 2; i <= $#; i++ )); do
if [[ ${!i} -ne 0 ]]; then
if [[ ${!i} -ne 1 ]]; then
echo "$i was not 1 or 0. Please correct this then try again."
exit 1;
fi
fi
done
Note the i running from 2 to number of arguments $#. Also, note the use of recommended and less error-prone [[ .. ]] instead of [ .. ] (otherwise you would have to write [ "${!i}" -ne 0 ], etc).
A simpler solution which avoids the unnecessary indirect referencing looks like this:
#!/bin/bash
while [[ $2 ]]; do
if (( $2 != 0 && $2 != 1 )); then
echo "$2 is neither 0, nor 1"
exit 1
fi
shift
done
We start checking the second argument ($2), use the arithmetic expression (( expr )) testing of value of the second argument, and shift positional arguments to the left by 1 at each iteration (now $3 becomes $2, etc).

Shell program to find the Factorial of a Number is NOT working

I am relatively new to Shell Programming. I am trying to find the Factorial of a number passed as an Argument of my program. The script so far is:
#!/bin/bash
number=$1
factorial=1
i=$number
while [ $i != 1 && $i != 0 ]
do
factorial=`expr $factorial \* $i`
i=` expr $i – 1 `
done
echo “Factorial of $number is $factorial”
But as I am executing the program :
sh fact.sh 5
It says:
Fact.sh: 10: [: missing ]
“Factorial of 5 is 1”
What am I doing wrong here?
When you use [ in your shell scripts you are actually using the test command and you are not really going by its rules.
Try doing a man test to find out the syntax of the test command.
You have other problems in your script but overcoming these is all part of learning and I hope my little hint helps you on your path of learning.
You have some answers to the immediate problem.
A quick rewrite to demonstrate some bash features
#!/bin/bash
number=$1
declare -i factorial=1
declare -i i
for ((i=number; i != 1 && $i != 0; i--)); do
(( factorial = factorial * i ))
done
echo “Factorial of $number is $factorial”
Notes
use ((...)) for arithmetic evaluation
don't need to use $ to refer to variables within an arithmetic expression
don't use fancy quotes (”), they have no special meaning to the shell.
I'm using declare to inform the shell that these are integers
(I don't know how much this will improve performance)
using a for loop instead of a while loop for no particular reason.
Expression while [ $i != 1 && $i != 0 ] won't work.
Use while [ $i != 1] && [ $i != 0 ] instead or, if you're using BASH, while [[ $i != 1 && $i != 0 ]]

If statement in shell

I'm making a script that executes a file a certain amount of times, then if the file executes properly, it generates another file, and this last file I need to store it in a directory. Example: I execute it like this: ./shell 5 DIR
Then, the crpit shell executes another file called simulation 5 times and it creates a directory and an output file for each time the file simulation was executed correctly.
The thing is, I have to put an if statement for the parameters they send when they execute the file, and I don't know how to, here's the code I have:
#!/bin/bash
if [ $# == 3 || $# == 2 ]; then
c=0
i=0
e=0
cont=0
while [ $c -le $0 ]
do
./simula cont RES
e = $?
if[ e == 0 ]; then
if[ $# == 3 ]; then
chmod RES $2
mkdir $1$c
mv RES $1$c/.
(( c++ ))
else
(( i++ ))
(( cont++ ))
done
echo Shan generat $c simulacions correctes.
echo Hi ha hagut $i simulacions erronies.
else
echo Nombre de parametres incorrecte: $#.
fi
So I must have either 2 or 3 paremeters when executing the file called shell, and thats why i have that if in the second line of code, however it keeps giving me an error:
./shell: line 12: syntax error near unexpected token `then'
./shell: line 12: ` if[ e == 0 ]; then'
I'm new to scripts and I have no idea what am I doing wrong, any clues?
Spacing is very important in shell scripts, it's not like programming languages where
if(x>y) and if ( x > y ) are the same thing.
In particular, if and [ are two different commands:
if[ e == 0 ] needs to be if [ e == 0 ]
More precisely, if is a shell keyword as are then, else, and fi
[ is either a shell builtin or an external program (or both) depending on your shell and is an alias for test. You can find out more with the which and type commands.
$ type [
[ is a shell builtin
$ which [
/bin/[
$ type fi
fi is a shell keyword
$ which fi
$
Does it works if you try if [ $e == 0 ]; instead of if[ e == 0 ];
When assigning variables, Bash doesn't allow leaving spaces in between the = sign like is common practice in other languages.
There must be a space separating reserved words like if.
So if [ $# == 3 ] is valid where if[ $# == 3 ] is not.
In Bash, it's important to use $ in front of variable names when you're calling the variable after it's been assigned.
So [ e == 0 ] would test to see if the literal string e is the same as the string 0. To test if the string assigned to variable e is the same as the string 0, use [ $e == 0 ] .
When testing integers, you can use -eq instead of ==. See man test for more information.
Also, $0 expands to the name of the script file, so you're while loop will never end if the script name is not 0 as the script name will not change while your script is running.

What is the common name of this "binary" approach to counting? How should it be done?

The following script is a study from my work with a script that I use in order to scrape web pages with links to an unknown number of sequentially numbered files. In order to create as few useless requests as possible, the scraping script guesses the number of files in a "binary" way.
This "study" script optionally takes a guess as $1 as a point of departure in order to find the number of .txt files in a directory. As I was writing it, i had the feeling of reinventing the wheel.
What is this way of counting called? How could it be done less clumsily in Bash?
#!/bin/bash
set_SEARCH()
{
DIVIDED=$SEARCH
SEARCH=$(($SEARCH * 2))
}
increase_SEARCH()
{
SEARCH=$(($SEARCH + $DIVIDED))
DIVIDED=$(($DIVIDED / 2))
#echo $DIVIDED
}
decrease_SEARCH()
{
SEARCH=$(($SEARCH - $DIVIDED))
DIVIDED=$(($DIVIDED / 2))
#echo $DIVIDED
}
test_SEARCH()
{
while [ -f "$(($SEARCH * 2)).txt" ]
do
set_SEARCH
done
if [ -f "${SEARCH}.txt" ]
then
echo "Trying $SEARCH"
if [ "$DIVIDED" = 0 ]
then
NUMBER=$(($SEARCH + 0))
echo "The number is $NUMBER"
exit
fi
increase_SEARCH
test_SEARCH
else
echo "Trying $SEARCH"
if [ "$DIVIDED" = 0 ]
then
NUMBER=$(($SEARCH - 1))
echo "The number is $NUMBER"
exit
fi
decrease_SEARCH
test_SEARCH
fi
}
SEARCH=2
while [[ "$SEARCH" -le "$1" ]] ;
do
SEARCH=$(($SEARCH * 2))
done
DIVIDED=$(($SEARCH / 2))
test_SEARCH
This is binary search with unknown upper bound. Knowing this, we can easily write/adapt implementations:
upper=42 # an arbitrary number >= 1
lower=0
while [[ -f $upper.txt ]]
do
lower=$upper
(( upper*= 2 ))
done
while (( lower < upper-1 ))
do
middle=$(((lower+upper)/2))
if [[ -f $middle.txt ]]
then
lower=$middle
else
upper=$middle
fi
done
echo "The number is $lower"

Resources