I have this bash script that receives me the error mentioned below. Do you have any explanations?
#! /bin/bash
first=1
second=1
result=2
contor=2
fib()
{
contor=$1
if [ $contor -eq $n ]
then
echo result
else
let $contor++
let result=$first+$second
first=$second
second=$results
fib $contor
fi
}
read n
result=$(fib 2)
echo $reusult
I see at least three problems here:
let $contor++ will actually substitute the value of contor, meaning the expression will be something like let 42++, which is invalid. Get rid of the $.
second=$results will set second to something other than what's in result, which will probably cause an issue when you try to execute let result=$first+$second. Use the correct variable name.
You print the variable reusult at the end, again a mis-spelling.
The way I found those issues is the same way you (or anyone, for that matter) should be finding them in bash, by temporarily putting the following lines at the top of your script to aid debugging:
set -e # stop on error
set -x # echo each interpreted line before execution.
There's are a great help when trying to figure out where the problems lie.
For what it's worth, Fibonacci is usually a bad candidate for recursion since you generally have to calculate things more than once.
That's because calculating fib(1000) as fib(999) + fib(998) usually entails a massive duplication of effort on either side of that + symbol.
That's not actually the case with your code because, while the code is calling itself, the leakage of information across levels really makes it an iterative solution - at no stage are you getting the result of fib(n-1) and fib(n-2) to calculate fib(n).
So, since you have an iterative solution anyway, you may as well remove the last vestiges of recursion and use something like:
#! /bin/bash
fib() {
# Validate input argument to be integer 1...92.
num=$1
if [[ ! ${num} =~ ^[1-9][0-9]*$ ]] ; then
echo "Invalid input '${num}'."
return
fi
if [[ ${num} -gt 92 ]] ; then
echo "Invalid input '${num}'."
return
fi
# First two Fib's are both one.
if [[ ${num} -le 2 ]] ; then
echo "1"
return
fi
# For other Fib's, just iterate keeping previous two.
grandparent=1 ; parent=1 ; child=2
((num -= 3))
while [[ ${num} -gt 0 ]] ; do
((grandparent = parent))
((parent = child))
((child = grandparent + parent))
((num--))
done
echo ${child}
}
# Rather rudimentary test harness.
[[ "$(fib x)" != "Invalid input 'x'." ]] && echo Fail A && exit
[[ "$(fib -7)" != "Invalid input '-7'." ]] && echo Fail B && exit
[[ "$(fib 0)" != "Invalid input '0'." ]] && echo Fail C && exit
[[ "$(fib 93)" != "Invalid input '93'." ]] && echo Fail D && exit
[[ "$(fib 1)" != "1" ]] && echo Fail E && exit
[[ "$(fib 2)" != "1" ]] && echo Fail F && exit
[[ "$(fib 3)" != "2" ]] && echo Fail G && exit
[[ "$(fib 4)" != "3" ]] && echo Fail H && exit
[[ "$(fib 5)" != "5" ]] && echo Fail I && exit
[[ "$(fib 6)" != "8" ]] && echo Fail J && exit
[[ "$(fib 20)" != "6765" ]] && echo Fail K && exit
[[ "$(fib 50)" != "12586269025" ]] && echo Fail L && exit
[[ "$(fib 92)" != "7540113804746346429" ]] && echo Fail M && exit
# User test.
read -p "Which Fibonacci number (>= 1)? " n
result=$(fib $n)
echo $result
You'll notice the input is restricted to the range 1..92. That's because, at some point (92 is the last one that works for me), bash will start giving strange results because of limits of its internal data types.
Related
I'd like to write a script that takes 2 parameters,
The first is parameter that could be "alpha/beta/tcp_friendliness/fast_convergence".
The Second should be a number for the alpha/beta cases and a 0/1 for the other 2.
for example: ./manage_cc alpha 512
Now i've wrote the following script that supposedly covers my cases, but it seems to go into all the conditionals. surely my syntax is broken so any help would be appreciated.
#!/bin/bash
echo -n $2 > /sys/module/tcp_tuner/parameters/$1
if [["$1" == ""] || ["$2" == ""]]
then
echo "You need to pass a property to modify as a first parameter and a value as the second"
fi
if [["$1" == "alpha"] || ["$1" == "beta"]]
then
echo -n $2 > /sys/module/tcp_tuner/parameters/$1
else
if [["$1" == "tcp_friendliness"] || ["$1" == "fast_convergence"]]
then
if [["$2" != "0"] && ["$2" != "1"]]
then
echo "This parameter only accepts a boolean value (0/1)"
exit 1
else
echo -n $2 > /sys/module/tcp_tuner/parameters/$1
fi
else
echo "The only accepted values for first parameter are alpha/beta/tcp_friendliness/fast_convergence"
exit 1
fi
fi
A rewrite of your code:
#!/usr/bin/env bash
write() {
printf "%s" "$2" > "/sys/module/tcp_tuner/parameters/$1"
}
die() {
echo "$*" >&2
exit 1
}
main() {
[[ -z $2 ]] && die "You need to pass a property to modify as a first parameter and a value as the second"
case $1 in
alpha|beta)
write "$1" "$2"
;;
tcp_friendliness|fast_convergence)
if [[ "$2" == "0" || "$2" == "1" ]]; then
write "$1" "$2"
else
die "This parameter only accepts a boolean value (0/1)"
fi
;;
*) die "The only accepted values for first parameter are alpha/beta/tcp_friendliness/fast_convergence"
;;
esac
}
main "$#"
Your condition syntax is wrong. When you start a condition with [[ you have to end it with ]], not just ]. They don't nest like parentheses.
if [[ "$1" == "alpha" || "$1" == "beta" ]]
I would like to ask you and request for the help with this particular part of the script - I would like to make it more compact as I feel it is too long, or there might me a shorter syntax alternatives...
This part is cut from my script for creation of X amount of files with descending date (1 day jumps, so each file is 1 day older that previous), which is already 3x longer than the whole "implementation" part.
This part's purpose is to maintain, check and correct (appropriately) the arguments defined with the script execution, which looks like
./script.sh X X
whereas script expects two arguments (number indicating amount of files to create and the path to the folder, where it is supposed to create those0:
- if there is just 1 it assumes the user wants to create files in the present folder, therefore it checks, if the one included argument is a number and if it is it will add output from pwd to the path (in this case to not have some sort of "cross-mount" accident with the system variable PATH conveniently named PATHX), so there will be two arguments at the end anyway
- if there are more than 2, it will terminate the script
If there are 2 arguments, it performs additional "tasks:
1. checks if there are just two numbers, if so, script ends
2. checks if there are just two words/letters, if so, script ends
3. if there are, let's say, just two dots as arguments, it will end anyway as there is one if, which always will need one argument to be just number
4. after first steps this will save the arguments to the variables (it is necessary as I have had some problems in the implementation part), this step even coves any possible combination of the positions of the arguments (it does not matter now, if the amount is first or second; same for the path)
5. this is a very last step, which just (for any case) covers the case, the path included has not been inserted with the slash in front (as this is mandatory for bash to recognise it indicates an absolute path) or at the end (important for the touch command in the implementation path as the command looks like "touch -t *TIMESTAMP* "$PATHXfile$i""); together with it it does provide an appropriate action, if the path is defined with ".","..","./","../" - if there would be a full path, not relative one
if [[ $# -eq 2 ]]
then
if [[ $1 == ?(-)+([0-9]) ]] && [[ $2 == ?(-)+([0-9]) ]]
then
echo "Invalid arguments. Did you include a slash at the start of the path (if the file name consists only of the numbers)? Well, try it again. Terminating script..."
exit
elif [[ $1 == ?(-)+([a-z]) ]] && [[ $2 == ?(-)+([a-z]) ]]
then
echo "Only characters in the arguments. Is this some sort of a joke? If you have tried some sick hexadecimal format of number (just A-F), it will not work, mate. Terminating script..."
exit
fi
if [[ $1 == ?(-)+([0-9]) ]]
then
AMOUNT=$1
PATHX=$2
else
AMOUNT=$2
if [[ $AMOUNT != ?(-)+([0-9]) ]]
then
echo "No argument with number/numbers-only as an amount of files to create. Terminating script..."
exit
fi
PATHX=$1
fi
else
if [[ $# -eq 1 ]]
then
if [[ $1 == ?(-)+([0-9]) ]]
then
AMOUNT=$1
PATHX=$(pwd)
else
echo "Please, include the argument with an amount of files to create. Terminating script..."
exit
fi
else
echo "2 Arguments are expected. Terminating script..."
exit
fi
fi
if [[ $PATHX != /* ]]
then
if [[ "$PATHX" == "." ]] || [[ "$PATHX" == ".." ]] || [[ "$PATHX" == "./"* ]] || [[ "$PATHX" == "../"* ]]
then
true
else
PATHX=$(echo "/$PATHX")
fi
fi
if [[ $PATHX != */ ]]
then
PATHX=$(echo "$PATHX/")
fi
Excuse this formatting, but without adding the description to the code sample it is just a mess of text...
Anyway, thank you, Guys, for any input.
It may be reduced to:
[[ $# -gt 2 ]] && { echo "Incorrect number of arguments"; exit 1; }
[[ $1 == ?(-)+([0-9]) ]] || { echo "First argument is not a number"; exit 2; }
[[ $2 == ?(-)+([0-9]) ]] && { echo "Both arguments are numbers"; exit 3; }
[[ $2 == +([a-z]) ]] || { echo "File name must be only letters"; exit 4; }
[[ ! -d $2 ]] && { echo "Path does not exist"; exit 5; }
n=$1
p=${2:-$PWD} # Use the PWD if path is missing.
if [[ $p != */ ]]; then
echo "Missing trailing slash; correcting";
p+=/
fi
if [[ $p != /* ]]; then
echo "Missing leading slash; correcting";
[[ $p == #(.|..|./*|../*) ]] || p=/"$p"
fi
In bash, I'm trying to test whether a sentence is a pangram.
read sentence
if [[ "$sentence" == [Aa] && [Bb] && [Cc] && [Dd] && [Ee] && [Ff] && [Gg] && [Hh] && [Ii] && [Jj] && [Kk] && [Ll] && [Mm] && [Nn] && [Oo] && [Pp] && [Qq] && [Rr] && [Ss] && [Tt] && [Uu] && [Vv] && [Ww] && [Xx] && [Yy] && [Zz] ]]; then
echo "pangram"
else
echo "not pangram"
fi
This is the code I have so far, and all I'm getting is an output of "not pangram".
Does anyone know what is wrong with my code?
I was trying to manipulate the code from a previous question of mine.
A better and pure Bash way to test for a pangram would be (written as a function):
is_pangram() {
local l=${1,,} i
for i in {a..z}; do
[[ $l = *$i* ]] || return 1
done
return 0
}
This function first converts its argument to lowercase: the expansion of ${1,,} is that of $1 converted to lowercase; we store this value in the local variable l. We then loop through the (lowercase alphabet) with for i in {a..z} and we use a glob (instead of a regular expression which would be overkill in this case) to check whether $l contains the letter.
Then try it:
$ if is_pangram "Cwm fjord bank glyphs vext quiz"; then echo "it's a pangram"; else echo "not a pangram"; fi
it's a pangram
$ if is_pangram "the horse jumps over the fence"; then echo "it's a pangram"; else echo "not a pangram"; fi
not a pangram
Your syntax is almost right, but needs a bit more repetition. You'll need something like:
[[ "$sentence" =~ [Aa] && "$sentence" =~ [Bb] && "$sentence" =~ [Cc] && ... ]]
There are undoubtedly more succinct ways to do this.
Can you use common *nix commands, or are you restricted to pure bash operations and built-ins?
If sort is permitted, then I'd do:
#!/bin/bash
# Simple pangram tester.
# Doesn't handle non-alphabetic chars except space.
# Written by PM 2Ring 2014.10.21
is_pangram()
{
count=$(echo -n ${1// /}|(while read -n 1 a;do echo $a;done)|sort -fu|wc -l)
[[ $count -eq 26 ]]
}
test_pangram()
{
if is_pangram "$1"
then echo "'$1' is a pangram."
else echo "'$1' is not a pangram."
fi
}
teststrings=(
"A quick brown fox jumps over the lazy dog"
"This is a test"
"Cwm fjord bank glyphs vext quiz"
"Is not a pangram"
)
for s in "${teststrings[#]}"
do
test_pangram "$s"
done
I am attempting to run a block of code if one flag is set to true and the other is set to false. ie
var1=true
var2=false
if [[ $var1 && ! $var2 ]]; then var2="something"; fi
Since that did not evaluate the way that I expected I wrote several other test cases and I am having a hard time understanding how they are being evaluated.
aa=true
bb=false
cc="python"
if [[ "$aa" ]]; then echo "Test0" ; fi
if [[ "$bb" ]]; then echo "Test0.1" ; fi
if [[ !"$aa" ]]; then echo "Test0.2" ; fi
if [[ ! "$aa" ]]; then echo "Test0.3" ; fi
if [[ "$aa" && ! "$bb" ]]; then echo "Test1" ; fi
if [[ "$aa" && ! "$aa" ]]; then echo "Test2" ; fi
if [[ "$aa" ]] && ! [[ "$bb" ]]; then echo "test3" ; fi
if [[ "$aa" ]] && ! [[ "$cc" ]]; then echo "test4" ; fi
if [[ $aa && ! $bb ]]; then echo "Test5" ; fi
if [[ $aa && ! $aa ]]; then echo "Test6" ; fi
if [[ $aa ]] && ! [[ $bb ]]; then echo "test7" ; fi
if [[ $aa ]] && ! [[ $cc ]]; then echo "test8" ; fi
When I run the preceding codeblock the only output I get is
Test0
Test0.1
Test0.2
however, my expectation is that I would get
Test0
Test1
Test3
Test5
Test7
I have tried to understand the best way to run similar tests, however most examples I have found are set up in the format of
if [[ "$aa" == true ]];
which is not quite what I want to do. So my question is what is the best way to make comparisons like this, and why do several of the test cases that I would expect to pass simply not?
Thank you!
Without any operators, [[ only checks if the variable is empty. If it is, then it is considered false, otherwise it is considered true. The contents of the variables do not matter.
Your understanding of booleans in shell context is incorrect.
var1=true
var2=false
Both the above variables are true since those are non-empty strings.
You could instead make use of arithmetic context:
$ a=1
$ b=0
$ ((a==1 && b==0)) && echo y
y
$ ((a==0 && b==0)) && echo y
$
$ ((a && !(b))) && echo y; # This seems to be analogous to what you were attempting
y
The shell does not have Boolean variables, per se. However, there are commands named true and false whose exit statuses are 0 and 1, respectively, and so can be used similarly to Boolean values.
var1=true
var2=false
if $var1 && ! $var2; then var2="something"; fi
The difference is that instead of testing if var1 is set to a true value, you expand it to the name of a command, which runs and succeeds. Likewise, var2 is expanded to a command name which runs and fails, but because it is prefixed with ! the exit status is inverted to indicate success.
(Note that unlike most programming languages, an exit status of 0 indicates success because while most commands have 1 way to succeed, there are many different ways they could fail, so different non-zero values can be assigned different meanings.)
true and false are evaluated as strings ;)
[[ $var ]] is an equivalent of [[ -n $var ]] that check if $var is empty or not.
Then, no need to quote your variables inside [[. See this reminder.
Finally, here is an explication of the difference between && inside brackets and outside.
The closest you can come seems to be use functions instead of variables because you can use their return status in conditionals.
$ var1() { return 0; }
$ var2() { return 1; } # !0 = failure ~ false
and we can test this way
$ var1 && echo "it's true" || echo "it's false"
it's true
$ var2 && echo "it's true" || echo "it's false"
it's false
or this way
$ if var1; then echo "it's true"; else echo "it's false"; fi
it's true
$ if var2; then echo "it's true"; else echo "it's false"; fi
it's false
Hope this helps.
In this example I want it to know if $# contains two words/symbols "load" and "/"
for one word/symbol this works
case "$#" in */*)
;;
echo "going to do stuff"
*)
echo "will do something else"
;;
esac
or
string='My string';
if [[ "$string" == *My* ]]
then
echo "It's there!";
fi
But if two words/symbols appear at random places I cant figure out how to do it.
Update:
The input will the module command. In this case I want to know if it is the module load with or without / that indicate version. the command will look like this
1) module load appname/1.1.1 or
2) module load appname
3) module (not load) (list, avail etc)
It is number 1 I am interested in for now.
3 will in some cases be variation of 1.
2 will be run as is but will include a message to the user
The slow way would be to iterate through the array twice and then check if both "load" and "/" were present, like this:
for element in $#; do [[ "$element" == "load" ]] && loadPresent=1; done
for element in $#; do [[ "$element" =~ ".*/.*" ]] && slashPresent=1; done
if [[ $loadPresent == 1 ]] && [[ $slashPresent == 1 ]]; then
echo "Contains load and /"
fi
(As I interpreted your question you want one parameter to be exactly "load" and another one to contain a slash.)
Something like this is possible:
if [[ ${#} =~ .*/.* && ${#} =~ ((^)|([ ]))load(($)|([ ])) ]]
then
echo both
fi
-or-
if LOAD=0 && SLASH=0 && \
for ARG in ${#};
do
if [ "${ARG#*/}" != "${ARG}" ]; then SLASH=1; fi
if [ "${ARG}" = "load" ]; then LOAD=1; fi
done && [ "${LOAD}${SLASH}" = "11" ];
then
echo both
fi
-or-
function loadslash()
{
LOAD=0 && SLASH=0
for ARG in ${#};
do
if [ "${ARG#*/}" != "${ARG}" ]; then SLASH=1; fi
if [ "${ARG}" = "load" ]; then LOAD=1; fi
done
test "${LOAD}${SLASH}" = "11"
}
if loadslash ${#}
then
echo both
fi
this will satisfy your requirements
if [[ $1 == "load" ]]; then
if [[ $2 == */* ]]; then
do first case
else
do second case
fi
else
do third case
fi