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
Related
Well i need to compare all the strings as arguments of this shell script and say if all of them are equals or not so i try this
#!/bin/bash
#Ejercicio_4
if [ $# -ne 6 ]
then
echo Número de argumentos incorrecto
else
if [ $1 == $2 == $3 == $4 == $5 == $6 ]
then
echo Son iguales
else
echo No todas las palabras son iguales
fi
fi
also i try thinks like $# == $1 but this didnt work :(
As long as you're using bash, and comparing text strings, the [[..]] test is safer and more flexible. You can use && and || inside them for and/or operators. So this would work :
#!/bin/bash
#Ejercicio_4
if [ $# -ne 6 ]
then
echo Número de argumentos incorrecto
else
if [[ "$1" == "$2" && "$1" == "$3" && "$1" == "$4" && "$1" == "$5" && "$1" == "$6" ]]
then
echo Son iguales
else
echo No todas las palabras son iguales
fi
fi
Note also that "==" is not actually valid syntax in the old "[" test, although bash accepts it. You should properly only use the single-character "=" in single-bracket tests.
If you're comparing integers, however, you should use ((..)) instead.
But I strongly suggest you not follow this method, since when the number of arguments increase you will have to include a lot more conditions in the if statement which could get cumbersome. So prefer a loop and check the first arg with all others and see if they're equal, like given below :
#!/usr/bin/env bash
if [ $# -ne 6 ]
then
echo "Number of arguments are lesser than required number 6"
else
number_of_elements=$#
arguments=("$#")
first=("$1")
for (( i=1; i<number_of_elements; i++ )); do
if [[ "$first" != ${arguments[i]} ]];then
break
fi
done
if [[ $i -eq $number_of_elements ]];then
echo "All the argument strings are equal"
else
echo "Not equal"
fi
fi
The operator for string equality usually is =. And such chains aren't possible either. Check your man test.
Normally you'd use -a for "and" and check for each argument individually.
...
if [ $1 = $2 -a $1 = $3 -a $1 = $4 -a $1 = $5 -a $1 = $6 ]; then
...
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.
This question is different from the multitude of potential duplicates, and I've not been able to find this particular question answered or even asked...
Maybe I'm overlooking some simple logic here, but I'm running into the following issue:
I'm trying to have an if statement where conditions must be met like $V1 AND $V2 are TRUE || OR $V3 AND $V4 are TRUE. Here's a simple test:
#!/bin/bash
V1="File Placeholder"
#echo $V1
V2="May contain some text"
#echo $V2
V3="Some command output"
#echo $V3
V4="Command output contains this text"
#echo $V4
if [[ "$V1" ]] && [[ "$V2" == *"contain some"* ]] || [[ "$V3" ]] && [[ "$V4" == *"output contains"* ]]
then
echo "Hello $V1"
echo "World full of: $V2"
fi
Meaning, I'd like to do something if:
$V1 is true (a file exists)
AND
$V2 is true (some string is found)
OR
$V3 is true (i.e., not null)
AND
$V4 is true (command output contains text)
It appears to work a bit, but I realize it's not working properly: it won't return TRUE if both the second && conditions are FALSE ie: || [[ "$V6" ]] && [[ "$V4" == *"output contains"* ]] (why I think I may be overlooking some logic, maybe getting cancelled out somehow?).
Why isn't this working as I assume it would if a AND b are TRUE ... OR ... if x AND z are TRUE?
You should be using this snippet to group && conditions together in one [[ ... ]].
if [[ -n $V1 && $V2 == *"contain some"* ]] || [[ -n $V3 && $V4 == *"output contains"* ]]
then
echo "Hello $V1"
echo "World full of: $V2"
fi
Boolean conditions stop processing as soon as they have enough information to satisfy the test. So, if you have something like true && false && true, the last true will never be reached. A single false is enough to know that the whole test is going to be false, since you have to have all trues.
You might try adding (), which creates a subshell for each. This should effectively group the tests together, and allow the larger tests to be separate:
if ( [[ "$V1" ]] && [[ "$V2" == *"contain some"* ]] ) || ( [[ "$V3" ]] && [[ "$V4" == *"output contains"* ]] )
then
echo "Hello $V1"
echo "World full of: $V2"
fi
The important thing is to remember to keep your conditions separate.
I ultimately chose to use an 'order of evaluation' approach as firstly commented by #Lino referencing the third example in this answer groups of compound conditions in Bash test
if [[ "$V6" && "$V2" == *"contain some"* || ( "$V3" && "$V4" == *"output contains"* ) ]]
then
echo "Hello $V1"
echo "World full of: $V2"
fi
If this is a bad approach for some reason, I would appreciate any feedback.
I like how it is all contained within a single [[ ]] double bracket, and remains readable.
All answers and comments have been very helpful and enlightening! #DKing's answer suggesting to use subshells, #anubhava's answer cleanly combining the statements, and all of #chepner's comments!
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.
I'm stuck in something in my bash script, I have a string that composes of a repetitive sequence of 20s, e.g. 202020, there might be more or less 20s, e.g. 2020 or 2020202020, I want to create an if condition that if finds any strange number inside, e.g. 30 in 20203020, gives an error.
Any ideas?
this should do the test:
[[ "$var" =~ "^(20)+$" ]]
check this:
kent$ [[ "202" =~ "^(20)+$" ]] && echo "y" || echo "n"
n
kent$ [[ "20203" =~ "^(20)+$" ]] && echo "y" || echo "n"
n
kent$ [[ "20202002" =~ "^(20)+$" ]] && echo "y" || echo "n"
n
kent$ [[ "20202020" =~ "^(20)+$" ]] && echo "y" || echo "n"
y
how about this example, can you use it?
if [ "`echo "202020302020" | sed -e 's/20//g'`" ];
then echo "there is something";
fi
Extended patterns are a tiny bit more compact than regular expressions.
shopt -s extglob
if [[ $str = +(20) ]]; then
echo "twenties"
else
echo "mismatch"
fi
At some point, bash changed to treat all patterns in [[...]] expressions as extended patterns, so the shopt command may not be necessary.