Decrement variables that contain letters - linux

I have a set of valid characters [0-9a-zA-Z] and a variable that is assigned one of these characters. What I want to do is to be able to decrement that variable to the next in the set.
I can't figure out how to decrement letters , it works for numbers only.
#!/bin/bash
test=b
echo $test # this shows 'b'
let test-=1
echo $test # I want it to be 'a'

The advantage of
test=$(tr 1-9a-zA-Z 0-9a-zA-Y <<<"$test")
is that it correctly (I think) decrements a to 9 and A to z. And if that is not the order you want, it is easy to adjust.
See man tr for details. This is the Gnu version of tr; character ranges are not guaranteed by Posix, but most tr implementations have them. <<< "here strings" are also a common extension, which bash implements.

test=$(printf "\\$(printf '%03o' "$(($(printf '%d' "'$test") - 1 ))")")

you could try this:
#!/bin/bash
test=b
if [[ $test == A || $test == a || $test == 0 ]]
then
echo "character already at lowest value"
else
# convert $test to decimal digit
test_digit=$(printf '%d' "'$test")
decremented=$(( test_digit - 1 ))
# print $decremented as a char
printf "\\$(printf '%03o' "$decremented")\n"
fi
reference:
http://mywiki.wooledge.org/BashFAQ/071

If we set a variable (say a) to the whole string of characters:
$ a=$( IFS=''; set -- {0..9} {a..z} {A..Z}; echo "$*"); echo "$a"
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
We may take advantage of the fact that bash "arithmetic" may use a base up to 62 (in the same order as the letters presented).
$ test="A"; echo "${a:$((62#$test-1)):1}"
z
This works only for "one character" (and not zero 0).
It may be expanded to several characters, but that is not being asked.

Related

compare float numbers with integers to get output in shell programing? [duplicate]

I have this Bash script and I had a problem in line 16.
How can I take the previous result of line 15 and add
it to the variable in line 16?
#!/bin/bash
num=0
metab=0
for ((i=1; i<=2; i++)); do
for j in `ls output-$i-*`; do
echo "$j"
metab=$(cat $j|grep EndBuffer|awk '{sum+=$2} END { print sum/120}') (line15)
num= $num + $metab (line16)
done
echo "$num"
done
For integers:
Use arithmetic expansion: $((EXPR))
num=$((num1 + num2))
num=$(($num1 + $num2)) # Also works
num=$((num1 + 2 + 3)) # ...
num=$[num1+num2] # Old, deprecated arithmetic expression syntax
Using the external expr utility. Note that this is only needed for really old systems.
num=`expr $num1 + $num2` # Whitespace for expr is important
For floating point:
Bash doesn't directly support this, but there are a couple of external tools you can use:
num=$(awk "BEGIN {print $num1+$num2; exit}")
num=$(python -c "print $num1+$num2")
num=$(perl -e "print $num1+$num2")
num=$(echo $num1 + $num2 | bc) # Whitespace for echo is important
You can also use scientific notation (for example, 2.5e+2).
Common pitfalls:
When setting a variable, you cannot have whitespace on either side of =, otherwise it will force the shell to interpret the first word as the name of the application to run (for example, num= or num)
num= 1 num =2
bc and expr expect each number and operator as a separate argument, so whitespace is important. They cannot process arguments like 3+ +4.
num=`expr $num1+ $num2`
Use the $(( )) arithmetic expansion.
num=$(( $num + $metab ))
See Chapter 13. Arithmetic Expansion for more information.
There are a thousand and one ways to do it. Here's one using dc (a reverse Polish desk calculator which supports unlimited precision arithmetic):
dc <<<"$num1 $num2 + p"
But if that's too bash-y for you (or portability matters) you could say
echo $num1 $num2 + p | dc
But maybe you're one of those people who thinks RPN is icky and weird; don't worry! bc is here for you:
bc <<< "$num1 + $num2"
echo $num1 + $num2 | bc
That said, there are some unrelated improvements you could be making to your script:
#!/bin/bash
num=0
metab=0
for ((i=1; i<=2; i++)); do
for j in output-$i-* ; do # 'for' can glob directly, no need to ls
echo "$j"
# 'grep' can read files, no need to use 'cat'
metab=$(grep EndBuffer "$j" | awk '{sum+=$2} END { print sum/120}')
num=$(( $num + $metab ))
done
echo "$num"
done
As described in Bash FAQ 022, Bash does not natively support floating point numbers. If you need to sum floating point numbers the use of an external tool (like bc or dc) is required.
In this case the solution would be
num=$(dc <<<"$num $metab + p")
To add accumulate possibly-floating-point numbers into num.
In Bash,
num=5
x=6
(( num += x ))
echo $num # ==> 11
Note that Bash can only handle integer arithmetic, so if your AWK command returns a fraction, then you'll want to redesign: here's your code rewritten a bit to do all math in AWK.
num=0
for ((i=1; i<=2; i++)); do
for j in output-$i-*; do
echo "$j"
num=$(
awk -v n="$num" '
/EndBuffer/ {sum += $2}
END {print n + (sum/120)}
' "$j"
)
done
echo "$num"
done
I always forget the syntax so I come to Google Search, but then I never find the one I'm familiar with :P. This is the cleanest to me and more true to what I'd expect in other languages.
i=0
((i++))
echo $i;
I really like this method as well. There is less clutter:
count=$[count+1]
#!/bin/bash
read X
read Y
echo "$(($X+$Y))"
You should declare metab as integer and then use arithmetic evaluation
declare -i metab num
...
num+=metab
...
For more information, see 6.5 Shell Arithmetic.
Use the shell built-in let. It is similar to (( expr )):
A=1
B=1
let "C = $A + $B"
echo $C # C == 2
Source: Bash let builtin command
Another portable POSIX compliant way to do in Bash, which can be defined as a function in .bashrc for all the arithmetic operators of convenience.
addNumbers () {
local IFS='+'
printf "%s\n" "$(( $* ))"
}
and just call it in command-line as,
addNumbers 1 2 3 4 5 100
115
The idea is to use the Input-Field-Separator(IFS), a special variable in Bash used for word splitting after expansion and to split lines into words. The function changes the value locally to use word-splitting character as the sum operator +.
Remember the IFS is changed locally and does not take effect on the default IFS behaviour outside the function scope. An excerpt from the man bash page,
The shell treats each character of IFS as a delimiter, and splits the results of the other expansions into words on these characters. If IFS is unset, or its value is exactly , the default, then sequences of , , and at the beginning and end of the results of the previous expansions are ignored, and any sequence of IFS characters not at the beginning or end serves to delimit words.
The "$(( $* ))" represents the list of arguments passed to be split by + and later the sum value is output using the printf function. The function can be extended to add scope for other arithmetic operations also.
#!/usr/bin/bash
#integer numbers
#===============#
num1=30
num2=5
echo $(( num1 + num2 ))
echo $(( num1-num2 ))
echo $(( num1*num2 ))
echo $(( num1/num2 ))
echo $(( num1%num2 ))
read -p "Enter first number : " a
read -p "Enter second number : " b
# we can store the result
result=$(( a+b ))
echo sum of $a \& $b is $result # \ is used to espace &
#decimal numbers
#bash only support integers so we have to delegate to a tool such as bc
#==============#
num2=3.4
num1=534.3
echo $num1+$num2 | bc
echo $num1-$num2 | bc
echo $num1*$num2 |bc
echo "scale=20;$num1/$num2" | bc
echo $num1%$num2 | bc
# we can store the result
#result=$( ( echo $num1+$num2 ) | bc )
result=$( echo $num1+$num2 | bc )
echo result is $result
##Bonus##
#Calling built in methods of bc
num=27
echo "scale=2;sqrt($num)" | bc -l # bc provides support for calculating square root
echo "scale=2;$num^3" | bc -l # calculate power
#!/bin/bash
num=0
metab=0
for ((i=1; i<=2; i++)); do
for j in `ls output-$i-*`; do
echo "$j"
metab=$(cat $j|grep EndBuffer|awk '{sum+=$2} END { print sum/120}') (line15)
let num=num+metab (line 16)
done
echo "$num"
done
Works on MacOS. bc is a command line calculator
#!/bin/bash
sum=0
for (( i=1; i<=5; i++ )); do
sum=$(echo "$sum + 1.1" | bc) # bc: if you want to use decimal
done
echo "Total: $sum"

Count number of specific characters in input string

Im trying to count the number of letters, numbers and special characters in an input string
The user would type the string and then finish with a * to finish the program should then display a count for the number of letters numbers and special characters
So far i have this but i get errors on line 21 which i think is the else statement
The exact error message i get is "./masher3: line 21: 0: command not found"
#!/bin/bash
numcount=0
charcount=0
othercount=0
echo "Input string"
for char in $#
do
if [[ $char == "*" ]]
then
break
elif [[ $char == '0-9' ]]
then
$numcount = $numcount + 1
elif [[ $char == 'A-Z' ]]
then
$charcount = $charcount + 1
else
$othercount = $othercount + 1 <----- Error on this line
fi
done
echo $charcount
This program is written in pure bash (without calling any external programs).
Also look below the code – I added some more information.
#!/bin/bash
# Print the message without going to next line (-n)
echo -n "Your input string: "
# Read text from standard input. ‘-d '*'’ stops
# reading at first ‘*’ character. Remove it to
# terminate on press of key Enter.
# Result is stored in variable $input.
#
# -e enables backspace and other keys.
read -ed '*' input
# Jump to next line
echo
# Fill all counters with zeros
letters=0
digits=0
spaces=0
others=0
# While $input contains some text…
while [[ -n "$input" ]]
do
# Get the first character
char="${input:0:1}"
# Take everything from $input except
# the first character and store it again
# in $input
input="${input:1}"
# Is the character space?
if [[ "$char" == " " ]]
then
# Increase the $spaces variable by one
((spaces++))
# Else: If the $char after removal of all
# letters in (english) alphabet is empty string?
# That will be true when the $char is letter.
elif [[ -z "${char//[abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ]/}" ]]
then
# Increase $letters
((letters++))
# Else: If the $char …
# Just the same for digits
elif [[ -z "${char//[0123456789]/}" ]]
then
((digits++))
# Else increase the $others variable
else
((others++))
fi
done
# Show values
echo "Letters: $letters"
echo "Digits: $digits"
echo "Spaces: $spaces"
echo "Other characters: $others"
Also open/download the Bash Reference Manual (available as single page, plaintext, PDF). You probably have one copy already installed if you use Linux. Try commands info bash (usually shows hypertext browser if installed) or man bash (single page documentation but usually the same). It is sometimes hard to understand for beginners but you will learn more information about this programming language.
Bash has many builtin commands (such as read, [[, echo, printf etc.) that work like ordinary commands. Their help is in the Reference Manual or can be shown by typing help command_name in your bash shell.
See my other answer for solution.
Your program is quite weird.
Assignment into variable looks like variable=42 (not $variable = 42)
You have to use $((…)) syntax to perform calculations
[[ $char == '0-9' ]] means “When the character is exactly 0-9
$char contains separate arguments of the program, not characters in input.
$othercount = … means “run command with name specified in variable $othercount with arguments = and ….
Assigning into variable in bash
You have to NOT write $ before variable name when you want to assign to it and you have to have NO whitespace before =:
my_variable=42
variable_2=$(($my_variable + 8))
echo $my_variable # Prints “50”

BASH: how to pass in arguments to an alias: CANNOT USE A FUNCTION - syntax of Bash conditionals

This question differs in that the classic "use a function" answer WILL NOT work. Adding a note to an existing Alias question is equivalent to sending a suggestion e-mail to Yahoo.
I am trying to write macros to get around BASH's horrendous IF syntax. You know, the [, [[, ((...BASH: the PHP of flow control...just add another bracket. I'm still waiting for the "(((((((" form. Not quite sure why BASH didn't repurpose "(", as "(" has no real semantics at the if statement.
The idea is to have named aliases for [, [[ and (( , as each one of these durned test-ish functions has a frustratingly different syntax. I honestly can never remember which is which (how COULD you? It's completely ad hoc!), and good luck trying to google "[[".
I would then use the names as a mnemonic, and the alias to get rid of the completely awful differences in spacing requirements. Examples: "whatdoyoucallthisIf" for "((", "shif" (for shell if), "mysterydoublesquarebacketif" for that awful [[ thing which seems to mostly do the same thing as [, only it doesn't.
Thus, I MUST have something of the form:
alias IFREPLACEMENT="if [ \$# ]; then"
But obviously not \$#, which would just cement in the current argument list to the shell running the alias.
Functions will not work in this case, as the function:
function IFREPLACEMENT {
if [ $# ]; then
}
is illegal.
In CSH, you could say
alias abc blah blah !*
!1, etc. Is there ANYTHING in BASH that is similar (no, !* doesn't work in BASH)?
Or am [ "I just out of luck" ]; ?
As an aside, here are some of the frustrating differences involving test-ish functions in BASH that I am trying to avoid by using well-defined aliases that people would have to use instead of picking the wrong "[[", "[" or "((":
"((" is really creepy...if a variable contains the name of another variable, it's derferenced for as many levels as necessary)
"((" doesn't require a spaces like '[' and '[['
"((" doesn't require "$" for variables to be dereferenced
['s "-gt" is numeric or die. [[ seems to have arbitrary restrictions.
'[' and '[[' use ">" (etc) as LEXICAL comparison operators, but they have frustratingly different rules that make it LOOK like they're doing numeric comparisons when they really aren't.
for a variable: a="" (empty value), [ $a == 123 ] is a syntax error, but (( a == 123 )) isn't.
Sure, functions will work, but not like a macro:
function IFREPLACEMENT {
[[ "$#" ]]
}
IFREPLACEMENT "$x" = "$y" && {
echo "the same
}
FWIW, here's a brutal way to pass arguments to an alias.
$ alias enumerate='bash -c '\''for ((i=0; i<=$#; i++)); do arg=${!i}; echo $i $arg; done'\'
$ enumerate foo bar baz
0 foo
1 bar
2 baz
Clearly, because a new bash shell is spawned, whatever you do won't have any effect on the current shell.
Update: Based on feedback from #konsolebox, the recommendation is now to always use [[...]] for both simplicity and performance (the original answer recommended ((...)) for numerical/Boolean tests).
#Oliver Charlesworth, in a comment on the question, makes the case for not trying to hide the underlying bash syntax - and I agree.
You can simplify things with the following rules:
Always use [[ ... ]] for tests.
Only use [ ... ] if POSIX compatibility is a must. If available, [[ ... ]] is always the better choice (fewer surprises, more features, and almost twice as fast[1]).
Use double-quoted, $-prefixed variable references - for robustness and simplicity (you do pay a slight performance penalty for double-quoting, though1) - e.g., "$var"; see the exceptions re the RHS of == and =~ below.
Whitespace rules:
ALWAYS put a space after the initial delimiter and before the closing delimiter of conditionals (whether [[ / (( or ]] / )))
NEVER put spaces around = in variable assignments.
These rules are more restrictive than they need to be - in the interest of simplification.
Tips and pitfalls:
Note that for numeric comparison with [[ ... ]], you must use -eq, -gt, -ge, -lt, -le, because ==, <, <=, >, >= are for lexical comparison.
[[ 110 -gt 2 ]] && echo YES
If you want to use == with pattern matching (globbing), either specify the entire RHS as an unquoted string, or, at least leave the special globbing characters unquoted.
[[ 'abc' == 'a'* ]] && echo YES
Similarly, performing regex matching with =~ requires that either the entire RHS be unquoted, or at least leave the special regex chars. unquoted - if you use a variable to store the regex - as you may have to in order to avoid bugs with respect to \-prefixed constructs on Linux - reference that variable unquoted.
[[ 'abc' =~ ^'a'.+$ ]] && echo YES
re='^a.+$'; [[ 'abc' =~ $re ]] && echo YES # *unquoted* use of var. $re
An alternative to [[ ... ]], for purely numerical/Boolean tests, is to use arithmetic evaluation, ((...)), whose performance is comparable to [[ (about 15-20% slower1); arithmetic evaluation (see section ARITHMETIC EVALUATION in man bash):
Allows C-style arithmetic (integer) operations such as +, -, *, /, **, %, ...
Supports assignments, including increment and decrement operations (++ / --).
No $ prefix required for variable references.
Caveat: You still need the $ in 2 scenarios:
If you want to specify a number base or perform up-front parameter expansion, such as removing a prefix:
var=010; (( 10#$var > 9 )) && echo YES # mandate number base 10
var=v10; (( ${var#v} > 9 )) && echo YES # strip initial 'v'
If you want to prevent recursive variable expansion.
((...), curiously, expands a variable name without $ recursively, until its value is not the name of an existing variable anymore:
var1=10; var2=var1; (( var2 > 9 )) && echo YES
var2 expands to 10(!)
Has laxer whitespace rules.
Example: v1=0; ((v2 = 1 + ++v1)) && echo YES # -> $v1 == 1, $v2 == 2
Caveat: Since arithmetic evaluation behaves so differently from the rest of bash, you'll have to weigh its added features against having to remember an extra set of rules. You also pay a slight performance penalty1.
You can even cram arithmetic expressions, including assignments, into [[ conditionals that are based on numeric operators, though that may get even more confusing; e.g.:
v1=1 v2=3; [[ v1+=1 -eq --v2 ]] && echo TRUE # -> both $v1 and $v2 == 2
Note: In this context, by 'quoting' I mean single- or double-quoting an entire string, as opposed to \-escaping individual characters in a string not enclosed in either single- or double quotes.
1:
The following code - adapted from code by #konsolebox - was used for performance measurements:
Note:
The results can vary by platform - numbers are based on OS X 10.9.3 and Ubuntu 12.04.
[[ being nearly twice as fast as [ (factor around 1.9) is based on:
using unquoted, $-prefixed variable references in [[ (using double-quoted variable references slows things down somewhat)
(( is slower than [[ with unquoted, $-prefixed variable on both platforms: about 15-20% on OSX, around 30% on Ubuntu. On OSX, using double-quoted, $-prefixed variable references is actually slower, as is not using the $ prefix at all (works with numeric operators). By contrast, on Ubuntu, (( is slower than all ]] variants.
#!/usr/bin/env bash
headers=( 'test' '[' '[[/unquoted' '[[/quoted' '[[/arithmetic' '((' )
iterator=$(seq 100000)
{
time for i in $iterator; do test "$RANDOM" -eq "$RANDOM"; done
time for i in $iterator; do [ "$RANDOM" -eq "$RANDOM" ]; done
time for i in $iterator; do [[ $RANDOM -eq $RANDOM ]]; done
time for i in $iterator; do [[ "$RANDOM" -eq "$RANDOM" ]]; done
time for i in $iterator; do [[ RANDOM -eq RANDOM ]]; done
time for i in $iterator; do (( RANDOM == RANDOM )); done
} 2>&1 | fgrep 'real' | { i=0; while read -r line; do echo "${headers[i++]}: $line"; done; } | sort -bn -k3.3 | awk 'NR==1 { baseTime=substr($3,3) } { time=substr($3,3); printf "%s %s%%\n", $0, (time/baseTime)*100 }' | column -t
Outputs times from fastest to slowest, with slower times also expressed as a percentage of the fastest time.

How this AWK is replacing an IF?

I've reviewing some bash scripts written by other people at work and I found this line that I'm trying to understand
[[ $(awk 'BEGIN{print ('$CAPACITY'>=0.9)}') -eq 1 ]] && echo "Capacity at 90 Percent"
Is my understanding that this line is replacing an if statement. Could someone help me out explaining what this line really does. Thanks
This makes me very sad and pessimistic about the future of civilization...
Let's break this down:
[[ $(awk 'BEGIN{print ('$CAPACITY'>=0.9)}') -eq 1 ]] && echo "Capacity at 90 Percent"
Note the $(....). This tells the shell to execute the program inside, and replace the contents of $(...) with the value. For example:
$ file_name="/usr/local/bin/foo"
$ short_name="$(basename $file_name)"
$ echo $short_name
foo
In this the second line, we are running the command basename $file_name. This returns foo. Then, the shell will substitute foo for $(basename $filename) before assigning short_name. Here's the same thing with the debugger on:
$ set -xv
$ file_name="/usr/local/bin/foo"
foo=/usr/local/bin/foo
+ foo=/usr/local/bin/foo
$ short_name=$(basename $file_name)
short_name=$(basename $file_name)
basename $file_name
++ basename /usr/local/bin/foo
+ short_name=foo
$ echo $short_name
echo $short_name
+ echo foo
foo
$ set +xv # Turn off the debugger.
You can see how the shell executes $(...) and replaces it.
Thus, the user is actually running the program:
awk 'BEGIN{print ('$CAPACITY'>=0.9)}'
However, take a look at the quotation marks:
awk 'BEGIN{print ('$CAPACITY'>=0.9)}'
+++++++++++++ +++++++
The stuff with the pluses under it are part of the awk command inside single quotes and thus cannot be interpolated by the shell. HOWEVER, $CAPACITY is not in quotes. In other words, the value of $CAPACITY replaces that variable before the awk command is executed. Thus, if $CAPACITY is .8, the awk command will become:
awk 'BEGIN{print ('.8'>=0.9)}`
That's the very first part of the explanation.
Now on to the next part. How much do you know about awk?
Awk is a programming language that's usually part of Unix/Linux distributions. Awk normally works on files and assumes a loop around the file with each line being read in and operated on. For example:
$ awk '{print $1}` foo.txt
Let's assume that each line in foo.txt consists of several fields that are separated by spaces. The file foo.txt is read in and each line is passed through to the awk program and the awk program will print out the first field of each line.
However, there is no file for awk to operate on. This developer is using the special patter BEGIN. This is executed before any lines are read in. Since there is no file for awk to process, and there is no actual awk program (only a BEGIN statement), awk will execute this statement (assuming capacity is at 80%:
.8 >= .9
Like in Shell and other programming languages. This statement will evaluate as true or false. In awk, if this statement is true, it will a non-zero value (we hope 1). If it is false, it will equal zero. In this case, it will equal false.
Awk returns (like Perl) the last value it executes. Thus, if the capacity is at 80%, the awk statement .8 >= .9 will be false. Awk will return a zero.
Now, the entire $([[ $(awk 'BEGIN{print ('$CAPACITY'>=0.9)}') will be replaced with 0. Your [[ ... ]] test now becomes:
[[ 0 -eq 1 ]] && echo "Capacity at 90 Percent"
Well, [[ 0 -eq 1 ]] is false.
Now the final part.
The two commands && and || are list operators. Their name comes from the C programming operators of the same name, and the way C short circuits tests. For example,
if ( ( bar > 20 ) && ( foo < 30 ) ) {
is a typical C if statement. with foo and bar being variables. I am asking if bar is greater than 20 AND if foo is less than 30 to do something.
C will first evaluate bar > 20 and decide whether it is true or false. If bar > 20 is false, there's is no need to test foo < 30 because no matter what the results are, the statement is still false. What if bar is indeed greater than 20? You have to run the next part of the if statement.
Imagine this:
if ( ( bar > 20 ) || ( foo < 30 ) ) {
This says if bar is greater than 20 OR foo < 30. In this case, C will evaluate whether bar is greater than 20. If it is, there is no need to test whether or not foo is less than 30. The statement will be true no matter what the value of foo is. What if bar isn't greater than 20? Then, I have to test the value of foo.
So, if I have && and the first statement is false, don't do the second statement (the entire expression is false anyway). If the first statement is true, I have to run the second statement (because I don't know whether or not that entire statement is true or not).
If I have ||, the complete opposite happens. If the first statement is true, don't do the second statement (because the entire expression is true). If that first statement is false, I have to run the second statement.
The gist of this is:
[ "$foo" = "$bar" ] && echo "Foo equals bar"
is the same as:
if [ "$foo" = "$bar" ]
then
echo "Foo equals bar"
fi
Because if $foo does equal $bar, I have to execute the second part of the statement!
And, this:
[ "$foo" = "$bar" ] || echo "Foo and Bar are not equal"
is the same as:
if [ "$foo" != "$bar" ]
then
echo "Foo and Bar are not equal"
fi
So, first the shell substitutes in the value of the shell variable $CAPACITY into your little awk script.
Next the awk script runs testing whether or not the substituted value of $CAPACITY is greater than or equal to 0.9. Since there is no actual awk program, awk doesn't attempt to read in from STDIN.
Next, awk will assign a zero or non-zero value to that boolean statement (depending whether or not it's true). Then, the awk program will exit with the evaluated value of that boolean statement.
The shell now substitutes that zero or non-zero value for that entire $(...) phrase. This is run through a test to see if it is or isn't equal to 1.
Finally if that test statement is equal to 1, the && will tell the shell to evaluate that echo statement. Thus, if the shell variable $CAPACITY is .9 or greater, that echo statement will print.
That's a lot of machinations just to compare .8 (or whatever the capacity is) with .9, so why did the developer do this?
Probably because BASH shell can only do integer arithmetic. Since $CAPACITY is less than one, you can't do this:
if [[ $CAPACITY -le .9 ]]
then
echo "Capacity is at 90%"
fi
Instead of using awk, I would probably have used bc:
OVER_CAPACITY=$(bc <<<"$CAPACITY >= .9")
if [[ ! $OVER_CAPACITY -eq $(true) ]]
then
echo "Capacity is over 90%"
else
echo "Every thing is okay"
fi
It would have been a few more lines, but I hope it makes things a bit easier to understand and make the file easier to maintain.
The complete line can be thought of as [[ if something is true ]] &&=then do another thing
To understand what is going on in this code, turn on your mental shell parser, and find the innermost construct that will produce output. in this case
awk 'BEGIN{print ('$CAPACITY'>=0.9)}'
execute that on a cmd-line by itself. Obviously the variable CAPACITY has to be set with a value.
Then you can use the shell debug/trace facility (set -vx) to see every thing executing
CAPACITY=0.95
set -vx
[[ $(awk 'BEGIN{print ('$CAPACITY'>=0.9)}') -eq 1 ]] && echo "Capacity at 90 Percent"
+ awk 'BEGIN{print (0.95>=0.9)}'
+ [[ 1 -eq 1 ]]
+ echo 'Capacity at 90 Percent'
Capacity at 90 Percent
IHTH
It's not, the [[ and ]] are an improvement upon the test builtin and the && is an AND
So, what this line is doing equivalent to:
if [[ $(awk 'BEGIN{print ('$CAPACITY'>=0.9)}') -eq 1 ]] ; then
echo "Capacity at 90 Percent"
fi
In effect, the line is saying TEST this condition AND do this other thing only if it's TRUE
Similarly, you could do [[ something_to_test ]] || do this if something_to_test is false
which means, TEST this condition OR do this other thing
These are bash shell one-line shortcuts.
You got a lot of good explanations, now rewrite the whole thing as:
awk -v cap="$CAPACITY" 'BEGIN{ if (cap>=0.9) print "Capacity at 90 Percent" }'
for clarity and simplicity.
[[ .... ]] construct returns true or false.
so the exp in [[ .... ]] must be a logic operation

Linux command to do wild card matching

Is there any bash command to do something similar to:
if [[ $string =~ $pattern ]]
but that it works with simple wild cards (?,*) and not complex regular expressions ??
More info:
I have a config file (a sort of .ini-like file) where each line is composed of a wild card pattern and some other data.
For any given input string that my script receives, I have to find the first line in the config file where the wild card pattern matches the input string and then return the rest of the data in that line.
It's simple. I just need a way to match a string against wild card patterns and not RegExps since the patterns may contain dots, brackets, dashes, etc. and I don't want those to be interpreted as special characters.
The [ -z ${string/$pattern} ] trick has some pretty serious problems: if string is blank, it'll match all possible patterns; if it contains spaces, the test command will parse it as part of an expression (try string="x -o 1 -eq 1" for amusement). bash's [[ expressions do glob-style wildcard matching natively with the == operator, so there's no need for all these elaborate (and trouble-prone) tricks. Just use:
if [[ $string == $pattern ]]
There's several ways of doing this.
In bash >= 3, you have regex matching like you describe, e.g.
$ foo=foobar
$ if [[ $foo =~ f.ob.r ]]; then echo "ok"; fi
ok
Note that this syntax uses regex patterns, so it uses . instead of ? to match a single character.
If what you want to do is just test that the string contains a substring, there's more classic ways of doing that, e.g.
# ${foo/b?r/} replaces "b?r" with the empty string in $foo
# So we're testing if $foo does not contain "b?r" one time
$ if [[ ${foo/b?r/} = $foo ]]; then echo "ok"; fi
You can also test if a string begins or ends with an expression this way:
# ${foo%b?r} removes "bar" in the end of $foo
# So we're testing if $foo does not end with "b?r"
$ if [[ ${foo%b?r} = $foo ]]; then echo "ok"; fi
# ${foo#b?r} removes "b?r" in the beginning of $foo
# So we're testing if $foo does not begin with "b?r"
$ if [[ ${foo#b?r} = $foo ]]; then echo "ok"; fi
ok
See the Parameter Expansion paragraph of man bash for more info on these syntaxes. Using ## or %% instead of # and % respectively will achieve a longest matching instead of a simple matching.
Another very classic way of dealing with wildcards is to use case:
case $foo in
*bar)
echo "Foo matches *bar"
;;
bar?)
echo "Foo matches bar?"
;;
*)
echo "Foo didn't match any known rule"
;;
esac
John T's answer was deleted, but I actually think he was on the right track. Here it is:
Another portable method which will work in most versions of bash is
to echo your string then pipe to grep. If no match is found, it will
evaluate to false as the result will be blank. If something is returned,
it will evaluate to true.
[john#awesome]$string="Hello World"
[john#awesome]$if [[ `echo $string | grep Hello` ]];then echo "match";fi
match
What John didn't consider is the wildcard requested by the answer. For that, use egrep, a.k.a. grep -E, and use the regex wildcard .*. Here, . is the wildcard, and * is a multiplier meaning "any number of these". So, John's example becomes:
$ string="Hello World"
$ if [[ `echo $string | egrep "Hel.*"` ]]; then echo "match"; fi
The . wildcard notation is fairly standard regex, so it should work with any command that speaks regex's.
It does get nasty if you need to escape the special characters, so this may be sub-optimal:
$ if [[ `echo $string | egrep "\.\-\$.*"` ]]; then echo "match"; fi

Resources