Linux Bash scripting how to use a range of parameters - linux

how can i use a range of parameters in linux bash like 60-70 --> ($1 - $2)
case $1 in
$1-$2 )
echo "test"
shift;;
....) ....

Each case of a case statement is a pattern, but depending on the range, it can be tricky to specify a good pattern. 60-70, for instance, could be matched with
case $1 in
6[0-9] | 70) echo "test"
shift ;;
Essentially, you have to treat the numbers as digit strings, and match them as text. A range like 67-93, for instance, breaks down into the fairly unwieldy
6[7-9] | [78][0-9] | 9[0-3]

You can make a function for it.
When you have different ranges, make parameters like range(check, minimum, maximum).
Return 1 when check < minimimum, 0 when check inside boundaries
(or equal boundary) and 2 above:
range() {
if [ "$1" -lt "$2" ]; then
return 1
fi
if [ "$1" -gt "$3" ]; then
return 2
fi
return 0
}
When you want different returns for different ranges, change this function.
EDIT: Added quotes and made positive return values.
EDIT 2: changed function x into x()

Related

checking if a string is contained in other string in shell

if I run this, it doesnt print yes.
if [s == [Ss]]
then echo "yes"
fi
However, if I write double [], so will be like
if [[s == [Ss]]]
then echo "yes"
fi
it prints yes
I wonder WHY
Continuing from my comment, POSIX-shell does not allow the use of character classes (e.g. [...]) within test (synonymous with [...]). Only bash [[ ... ]] allows character classes within it.
If you have coreutils installed (about every distro does) you can still match a character list against the string returning the index (1-based) within the string of the first character in the character list. For your case you could use:
if [ $(expr index "s" "sS") -gt '0' ]; then
echo yes;
fi
Where expr index "s" "sS" is the usual form of index string charlist, which requires the use of expr before it.
If you are limited to POSIX shell, then you can sill use expr string : regex to match against a regular expression.
if [ $(expr "s" : '[sS]$') -gt '0' ]; then
echo yes;
fi
(note: the regex must match the entire contents of the string being tested against)
When comparing strings, the double-bracket syntax features shell globbing. As you are trying to compare with both the upper case and lower case.
Also the double bracket syntax helps in Regex pattern matching.
The condition you gave is similar to
if [[ "$stringvar" == *string* ]]; then
This means that an asterisk (“*”) will expand to literally anything, just as you probably know from normal command-line usage. Therefore, if $stringvar contains the phrase “string” anywhere, the condition will return true. Other forms of shell globbing are allowed, too. If you’d like to match both “String” and “string”, you could use the following syntax:
if [[ "$stringvar" == *[sS]tring* ]]; then
[ doesn't support pattern-matching. Use case instead.
inp=s
case $inp in
[sS]) echo "Matched s/S" ;;
*) echo "Did not match s/S" ;;
esac

Check if parameter is value X or value Y [duplicate]

This question already has answers here:
Bash If-statement to check If string is equal to one of several string literals [duplicate]
(2 answers)
Closed 6 years ago.
I want to check in my bash script, if a variable is equal to value 1 OR equal to value 2.
I don't want to use something like this, because the 'if true statements' are the same (some big echo texts), when the variable is equal to 1 or 2. I want to avoid data redundancy.
if [ $1 == 1 ] ; then echo number 1 ; else
if [ $1 == 2 ] ; then echo number 2 ; fi
More something like
if [ $1 == 1 OR 2 ] ; then echo number 1 or 2 ; fi
Since you are comparing integer values, use the bash arithmetic operator (()), as
(( $1 == 1 || $1 == 2 )) && echo "number 1 or 2"
For handling-strings using the regex operator in bash
test="dude"
if [[ "$test" =~ ^(dude|coolDude)$ ]]; then echo "Dude Anyway"; fi
# literally means match test against either of words separated by | as a whole
# and not allow for sub-string matches.
Probably the most easy to extend option is a case statement:
case $1 in
[12])
echo "number $1"
esac
The pattern [12] matches 1 or 2. For larger ranges you could use [1-5], or more complicated patterns like [1-9]|[1-9][0-9] to match any number from 1 to 99.
When you have multiple cases, you should separate each one with a ;;.

Decrement variables that contain letters

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.

How to sanitize user input in Bash

I am writing a script in bash which will prompt the user for two inputs. These inputs are assigned to the variables 'TO_SCHEMA' and 'FROM_SCHEMA' respectively.
I need a way to verify proper input. My requirements are as follows:
Each variable will have 3 acceptable values. They are the same values for both variables, but both variables must be in this list of three, and they cannot be the same value.
So if the values are 'myco', 'myco_int', and 'teambatch', then both variables must be one of those values, but they can't be the same.
${TO_SCHEMA} = myco && ${FROM_SCHEMA} = myco_int
Pass
${TO_SCHEMA} = myco_int && ${FROM_SCHEMA} = myco_int
Fail
${TO_SCHEMA} = mco && ${FROM_SCHEMA} = myco_int
Fail
${TO_SCHEMA} = myco && ${FROM_SCHEMA} = donkey
Fail
How can I accomplish this?
I began with an if statement full of AND and OR operators, but they got ugly fast. My experience with regex is limited, and my experience with sed and awk is non-existent, but I'm willing to learn and try any of that.
Any help would be appreciated.
EDIT:
I should also mention that this script is just for a somewhat small tedious one off task I have to do a lot at work that I would love to automate. If I'm not the one using it, then someone on my team will be. So this input checking is a want and not a need. It's not the end of the world if the script breaks because of bad input. I would just like it to handle bad input more elegantly.
EDIT AGAIN:
I appreciate everyone's suggestions, but I have to make some clarifications. The values won't actually be schema 1,2 and 3. I'm not allowed to provide proper names for security reasons, but I'm changing them to values more similar to the real ones.
The simplest solution requires bash 4.3. You simply store the valid inputs as the keys of an associative array, then use the -v operator to check if a given input is defined as a key.
declare -A valid
# Values don't matter, as long as the key exists.
# I put spaces in the keys just to show it's possible
valid[schema 1]=
valid[schema 2]=
valid[schema 3]=
if [[ $FROM_SCHEMA != $TO_SCHEMA && -v valid[$FROM_SCHEMA] && -v valid[$TO_SCHEMA] ]]; then
# inputs pass
else
# inputs fail
fi
In earlier 4.x versions, you can check for undefined values in an associative array slightly differently.
declare -A valid
# Now, we need to make sure the values are non-null, but
# otherwise it doesn't matter what they are
valid[schema 1]=1
valid[schema 2]=1
valid[schema 3]=1
if [[ $FROM_SCHEMA != $TO_SCHEMA && -n ${valid[$FROM_SCHEMA]} && -n ${valid[$TO_SCHEMA]} ]]; then
# inputs pass
else
# inputs fail
fi
Prior to bash 4, with no associative arrays, you can fall back to scanning the list of valid inputs stored in a regular array.
valid=("schema 1" "schema 2" "schema 3")
if [[ $TO_SCHEMA == $FROM_SCHEMA ]]; then
# inputs fail
else
ok_count=0
# We've already established that TO and FROM are different,
# so only at most one per candidate can match.
for candidate in "${valid[#]}"; do
if [[ $TO_SCHEMA == $candidate || $FROM_SCHEMA == $candidate ]]; then
ok_count+=1
fi
done
if (( ok_count == 2 )); then
# inputs pass
else
# inputs fail
fi
fi
Note, quick and dirty, lacking in elegance, but works. This is assuming your schema1 answers are accurate. And that I read your question right. you'd be replacing the [your input 1] etc with wherever you are getting the data from, it's a read of user input right? Why not just ask them to select it from a list?
values='1 2 3'
result1=''
result2=''
input1=[your input 1]
input2=[your input 2]
# force to lower case
input1=$(tr '[A-Z]' '[a-z]' <<< "$input1" )
input2=$(tr '[A-Z]' '[a-z]' <<< "$input2" )
for item in $values
do
if [[ -n $( grep -E "^schema$item$" <<< $input1 ) ]];then
values=$(sed "s/$item//" <<< $values )
result1=$input1
fi
done
for item in $values
do
if [[ -n $( grep -E "^schema$item$" <<< $input2 ) ]];then
result2=$input2
fi
done
if [[ -z $result1 ]];then
echo 'Your first entry is not right: ' $input1
elif [[ -z $result2 ]];then
echo 'Your second entry is not right: ' $input2
else
echo 'Valid data'
fi
Note that if you wanted to just test for the literal full string, you'd make it:
values='schema1 schema2 schema3'
then remove the 'schema' from the grep test, which would then just look for the full string thing. And sed would just remove the found item from the list of values.
If you are relying on user typed input, you must force it to lower to avoid spastic user actions or do a case insensitive pattern match with grep, but it's best to force it to lower before testing it.
input1=$(tr '[A-Z]' '[a-z]' <<< "$input1" )

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.

Resources